mirror of
https://github.com/Rogiel/l2jserver2
synced 2025-12-05 23:22:47 +00:00
Implements better schema management
This commit is contained in:
@@ -24,7 +24,9 @@ import com.l2jserver.model.id.ID;
|
||||
import com.l2jserver.service.Service;
|
||||
import com.l2jserver.service.ServiceConfiguration;
|
||||
import com.l2jserver.service.configuration.Configuration;
|
||||
import com.l2jserver.service.configuration.XMLConfigurationService.ConfigurationXPath;
|
||||
import com.l2jserver.service.core.threading.AsyncFuture;
|
||||
import com.mysema.query.sql.RelationalPath;
|
||||
import com.mysema.query.sql.RelationalPathBase;
|
||||
|
||||
/**
|
||||
@@ -50,6 +52,20 @@ public interface DatabaseService extends Service {
|
||||
* @see Configuration
|
||||
*/
|
||||
public interface DatabaseConfiguration extends ServiceConfiguration {
|
||||
/**
|
||||
* @return the update schema state
|
||||
*/
|
||||
@ConfigurationPropertyGetter(defaultValue = "true")
|
||||
@ConfigurationXPath("/configuration/services/database/automaticSchemaUpdate")
|
||||
boolean isAutomaticSchemaUpdateEnabled();
|
||||
|
||||
/**
|
||||
* @param updateSchema
|
||||
* the new uodate schema state
|
||||
*/
|
||||
@ConfigurationPropertySetter
|
||||
@ConfigurationXPath("/configuration/services/database/automaticSchemaUpdate")
|
||||
void setUpdateSchema(boolean updateSchema);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -122,8 +138,22 @@ public interface DatabaseService extends Service {
|
||||
* if any error occur while reading or parsing the file
|
||||
*/
|
||||
<M extends Model<?>, T extends RelationalPathBase<?>> void importData(
|
||||
Path path, T entity)
|
||||
throws IOException;
|
||||
Path path, T entity) throws IOException;
|
||||
|
||||
/**
|
||||
* Updates the given <code>schema</code> in the underlying storage engine.
|
||||
*
|
||||
* @param schema
|
||||
* the schema specification
|
||||
* @return true if the schema did not existed and was created (i.e. is empty
|
||||
* and ready to receive default data)
|
||||
*/
|
||||
boolean updateSchema(RelationalPath<?> schema);
|
||||
|
||||
/**
|
||||
* Updates all schemas in the underlying storage engine.
|
||||
*/
|
||||
void updateSchemas();
|
||||
|
||||
/**
|
||||
* Checks for the cached version of the object
|
||||
|
||||
@@ -32,13 +32,13 @@ import com.l2jserver.service.database.ddl.annotation.ColumnDefault;
|
||||
import com.l2jserver.service.database.ddl.annotation.ColumnNullable;
|
||||
import com.l2jserver.service.database.ddl.annotation.ColumnSize;
|
||||
import com.l2jserver.service.database.ddl.struct.Column;
|
||||
import com.l2jserver.service.database.ddl.struct.Column.ColumnType;
|
||||
import com.l2jserver.service.database.ddl.struct.ForeignKey;
|
||||
import com.l2jserver.service.database.ddl.struct.PrimaryKey;
|
||||
import com.l2jserver.service.database.ddl.struct.Table;
|
||||
import com.l2jserver.service.database.ddl.struct.Column.ColumnType;
|
||||
import com.l2jserver.util.ClassUtils;
|
||||
import com.l2jserver.util.factory.CollectionFactory;
|
||||
import com.mysema.query.sql.RelationalPathBase;
|
||||
import com.mysema.query.sql.RelationalPath;
|
||||
import com.mysema.query.types.Path;
|
||||
|
||||
/**
|
||||
@@ -47,15 +47,15 @@ import com.mysema.query.types.Path;
|
||||
*/
|
||||
public class TableFactory {
|
||||
/**
|
||||
* Creates an {@link Table} object from an {@link RelationalPathBase}
|
||||
* Creates an {@link Table} object from an {@link RelationalPath}
|
||||
*
|
||||
* @param tablePath
|
||||
* the table path
|
||||
* @return the {@link Table} object
|
||||
*/
|
||||
public static Table createTable(RelationalPathBase<?> tablePath) {
|
||||
public static Table createTable(RelationalPath<?> tablePath) {
|
||||
final Map<String, Column> columns = CollectionFactory.newMap();
|
||||
for (final Path<?> path : tablePath.all()) {
|
||||
for (final Path<?> path : tablePath.getColumns()) {
|
||||
final Column col = createColumn(tablePath, path);
|
||||
columns.put(col.getName(), col);
|
||||
}
|
||||
@@ -72,7 +72,8 @@ public class TableFactory {
|
||||
*
|
||||
* @param conn
|
||||
* the JDBC {@link Connection}
|
||||
* @param template the query template
|
||||
* @param template
|
||||
* the query template
|
||||
* @param tableName
|
||||
* the table name
|
||||
* @return the parsed table
|
||||
@@ -112,7 +113,7 @@ public class TableFactory {
|
||||
* the columns
|
||||
* @return the foreign key list
|
||||
*/
|
||||
private static List<ForeignKey> createFKs(RelationalPathBase<?> tablePath,
|
||||
private static List<ForeignKey> createFKs(RelationalPath<?> tablePath,
|
||||
Map<String, Column> columns) {
|
||||
final List<ForeignKey> fks = CollectionFactory.newList();
|
||||
for (final com.mysema.query.sql.ForeignKey<?> fk : tablePath
|
||||
@@ -130,15 +131,14 @@ public class TableFactory {
|
||||
return fks;
|
||||
}
|
||||
|
||||
private static PrimaryKey createPK(RelationalPathBase<?> tablePath,
|
||||
private static PrimaryKey createPK(RelationalPath<?> tablePath,
|
||||
Map<String, Column> columns) {
|
||||
return new PrimaryKey(columns.get(tablePath.getPrimaryKey()
|
||||
.getLocalColumns().get(0).getMetadata().getExpression()
|
||||
.toString()));
|
||||
}
|
||||
|
||||
private static Column createColumn(RelationalPathBase<?> tablePath,
|
||||
Path<?> path) {
|
||||
private static Column createColumn(RelationalPath<?> tablePath, Path<?> path) {
|
||||
final String columnName = path.getMetadata().getExpression().toString();
|
||||
final ColumnType columnType = getColumnType(path.getType());
|
||||
final Field field = ClassUtils.getFieldWithValue(tablePath, path);
|
||||
|
||||
@@ -16,12 +16,11 @@
|
||||
*/
|
||||
package com.l2jserver.service.database.orientdb;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@@ -48,13 +47,18 @@ import com.l2jserver.service.core.threading.ScheduledAsyncFuture;
|
||||
import com.l2jserver.service.core.threading.ThreadService;
|
||||
import com.l2jserver.service.database.DAOResolver;
|
||||
import com.l2jserver.service.database.DataAccessObject;
|
||||
import com.l2jserver.service.database.DatabaseException;
|
||||
import com.l2jserver.service.database.DatabaseService;
|
||||
import com.l2jserver.service.database.dao.DatabaseRow;
|
||||
import com.l2jserver.service.database.dao.InsertMapper;
|
||||
import com.l2jserver.service.database.dao.SelectMapper;
|
||||
import com.l2jserver.service.database.dao.UpdateMapper;
|
||||
import com.l2jserver.util.CSVUtils;
|
||||
import com.l2jserver.util.CSVUtils.CSVMapProcessor;
|
||||
import com.l2jserver.util.QPathUtils;
|
||||
import com.l2jserver.util.factory.CollectionFactory;
|
||||
import com.mysema.query.sql.ForeignKey;
|
||||
import com.mysema.query.sql.RelationalPath;
|
||||
import com.mysema.query.sql.RelationalPathBase;
|
||||
import com.mysema.query.types.Path;
|
||||
import com.orientechnologies.orient.core.db.document.ODatabaseDocumentPool;
|
||||
@@ -67,6 +71,7 @@ import com.orientechnologies.orient.core.metadata.schema.OType;
|
||||
import com.orientechnologies.orient.core.query.nativ.ONativeSynchQuery;
|
||||
import com.orientechnologies.orient.core.query.nativ.OQueryContextNative;
|
||||
import com.orientechnologies.orient.core.record.impl.ODocument;
|
||||
import com.orientechnologies.orient.core.tx.OTransaction;
|
||||
|
||||
/**
|
||||
* This is an implementation of {@link DatabaseService} that provides an layer
|
||||
@@ -122,6 +127,10 @@ public abstract class AbstractOrientDatabaseService extends AbstractService
|
||||
* every 1 minute.
|
||||
*/
|
||||
private ScheduledAsyncFuture autoSaveFuture;
|
||||
/**
|
||||
* The transactioned database connection, if any.
|
||||
*/
|
||||
private final ThreadLocal<ODatabaseDocumentTx> transaction = new ThreadLocal<ODatabaseDocumentTx>();
|
||||
|
||||
/**
|
||||
* Configuration interface for {@link AbstractOrientDatabaseService}.
|
||||
@@ -208,11 +217,13 @@ public abstract class AbstractOrientDatabaseService extends AbstractService
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
ensureDatabaseSchema();
|
||||
|
||||
database.close();
|
||||
|
||||
// check if automatic schema update is enabled
|
||||
if (config.isAutomaticSchemaUpdateEnabled()) {
|
||||
updateSchemas();
|
||||
}
|
||||
|
||||
// cache must be large enough for all world objects, to avoid
|
||||
// duplication... this would endanger non-persistent states
|
||||
objectCache = cacheService.createEternalCache("database-service",
|
||||
@@ -242,7 +253,25 @@ public abstract class AbstractOrientDatabaseService extends AbstractService
|
||||
|
||||
@Override
|
||||
public int transaction(TransactionExecutor executor) {
|
||||
return executor.perform();
|
||||
final ODatabaseDocumentTx database = ODatabaseDocumentPool.global()
|
||||
.acquire(config.getUrl(), config.getUsername(),
|
||||
config.getPassword());
|
||||
transaction.set(database);
|
||||
try {
|
||||
database.begin(OTransaction.TXTYPE.OPTIMISTIC);
|
||||
int returnValue = executor.perform();
|
||||
database.commit();
|
||||
return returnValue;
|
||||
} catch (DatabaseException e) {
|
||||
database.rollback();
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
database.rollback();
|
||||
throw new DatabaseException(e);
|
||||
} finally {
|
||||
transaction.set(null);
|
||||
database.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -267,56 +296,62 @@ public abstract class AbstractOrientDatabaseService extends AbstractService
|
||||
*/
|
||||
public <T> T query(Query<T> query) {
|
||||
Preconditions.checkNotNull(query, "query");
|
||||
final ODatabaseDocumentTx database = ODatabaseDocumentPool.global()
|
||||
.acquire(config.getUrl(), config.getUsername(),
|
||||
config.getPassword());
|
||||
|
||||
ODatabaseDocumentTx database = transaction.get();
|
||||
if (database == null)
|
||||
database = ODatabaseDocumentPool.global().acquire(config.getUrl(),
|
||||
config.getUsername(), config.getPassword());
|
||||
log.debug("Executing query {} with {}", query, database);
|
||||
try {
|
||||
return query.query(database, this);
|
||||
} finally {
|
||||
database.commit();
|
||||
if (transaction.get() == null)
|
||||
database.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <M extends Model<?>, T extends RelationalPathBase<?>> void importData(
|
||||
java.nio.file.Path path, T entity) throws IOException {
|
||||
java.nio.file.Path path, final T entity) throws IOException {
|
||||
final ODatabaseDocumentTx database = ODatabaseDocumentPool.global()
|
||||
.acquire(config.getUrl(), config.getUsername(),
|
||||
config.getPassword());
|
||||
|
||||
log.info("Importing {} to {}", path, entity);
|
||||
|
||||
BufferedReader reader = Files.newBufferedReader(path,
|
||||
Charset.defaultCharset());
|
||||
final String header[] = reader.readLine().split(",");
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
final String data[] = line.split(",");
|
||||
final ODocument document = new ODocument(database,
|
||||
entity.getTableName());
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
document.field(header[i], data[i]);
|
||||
}
|
||||
database.save(document);
|
||||
try {
|
||||
database.begin(OTransaction.TXTYPE.OPTIMISTIC);
|
||||
CSVUtils.parseCSV(path, new CSVMapProcessor<Object>() {
|
||||
@Override
|
||||
public Object process(Map<String, String> map) {
|
||||
final ODocument document = new ODocument(database, entity
|
||||
.getTableName());
|
||||
for (final Entry<String, String> entry : map.entrySet()) {
|
||||
document.field(entry.getKey(), entry.getValue());
|
||||
}
|
||||
database.save(document);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
database.commit();
|
||||
} catch (IOException | RuntimeException e) {
|
||||
database.rollback();
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
database.rollback();
|
||||
throw new DatabaseException(e);
|
||||
} finally {
|
||||
transaction.set(null);
|
||||
database.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure the database schema is up-to-date with the external database
|
||||
*/
|
||||
protected abstract void ensureDatabaseSchema();
|
||||
|
||||
/**
|
||||
* @param table
|
||||
* the {@link RelationalPathBase} table
|
||||
* @return true if a new schema was created
|
||||
*/
|
||||
protected boolean createSchema(RelationalPathBase<?> table) {
|
||||
@Override
|
||||
public boolean updateSchema(RelationalPath<?> table) {
|
||||
final ODatabaseDocumentTx database = ODatabaseDocumentPool.global()
|
||||
.acquire(config.getUrl(), config.getUsername(),
|
||||
config.getPassword());
|
||||
|
||||
log.info("Updating {} schema definition", table);
|
||||
|
||||
boolean newSchema = false;
|
||||
try {
|
||||
final OSchema schemas = database.getMetadata().getSchema();
|
||||
@@ -325,7 +360,7 @@ public abstract class AbstractOrientDatabaseService extends AbstractService
|
||||
schema = schemas.createClass(table.getTableName());
|
||||
newSchema = true;
|
||||
}
|
||||
for (final Path<?> path : table.all()) {
|
||||
for (final Path<?> path : table.getColumns()) {
|
||||
final String name = path.getMetadata().getExpression()
|
||||
.toString();
|
||||
OProperty property = schema.getProperty(name);
|
||||
@@ -335,10 +370,18 @@ public abstract class AbstractOrientDatabaseService extends AbstractService
|
||||
(path.getType().isEnum() ? OType.STRING : OType
|
||||
.getTypeByClass(path.getType())));
|
||||
if (path.getType().isEnum()) {
|
||||
property.setType(OType.STRING);
|
||||
if (property.getType() != OType.STRING)
|
||||
property.setType(OType.STRING);
|
||||
} else {
|
||||
property.setType(OType.getTypeByClass(path.getType()));
|
||||
if (property.getType() != OType.getTypeByClass(path
|
||||
.getType()))
|
||||
property.setType(OType.getTypeByClass(path.getType()));
|
||||
}
|
||||
final boolean nullable = QPathUtils.isNullable(path);
|
||||
if (property.isNotNull() != !nullable)
|
||||
property.setNotNull(!nullable);
|
||||
if (property.isMandatory() != !nullable)
|
||||
property.setMandatory(!nullable);
|
||||
}
|
||||
for (final ForeignKey<?> fk : table.getForeignKeys()) {
|
||||
final String[] columns = new String[fk.getLocalColumns().size()];
|
||||
@@ -347,8 +390,9 @@ public abstract class AbstractOrientDatabaseService extends AbstractService
|
||||
columns[i++] = keyPath.getMetadata().getExpression()
|
||||
.toString();
|
||||
}
|
||||
schema.createIndex(StringUtils.join(columns, "-"),
|
||||
INDEX_TYPE.NOTUNIQUE, columns);
|
||||
if (!schema.areIndexed(columns))
|
||||
schema.createIndex(StringUtils.join(columns, "-"),
|
||||
INDEX_TYPE.NOTUNIQUE, columns);
|
||||
}
|
||||
final String[] pkColumns = new String[table.getPrimaryKey()
|
||||
.getLocalColumns().size()];
|
||||
@@ -358,7 +402,8 @@ public abstract class AbstractOrientDatabaseService extends AbstractService
|
||||
pkColumns[i++] = keyPath.getMetadata().getExpression()
|
||||
.toString();
|
||||
}
|
||||
schema.createIndex("PRIMARY", INDEX_TYPE.UNIQUE, pkColumns);
|
||||
if (!schema.areIndexed(pkColumns))
|
||||
schema.createIndex("PRIMARY", INDEX_TYPE.UNIQUE, pkColumns);
|
||||
schemas.save();
|
||||
} finally {
|
||||
database.close();
|
||||
@@ -446,7 +491,6 @@ public abstract class AbstractOrientDatabaseService extends AbstractService
|
||||
private final InsertMapper<O, RI, I, E> mapper;
|
||||
private final Iterator<O> iterator;
|
||||
@SuppressWarnings("unused")
|
||||
// FIXME implement id generation
|
||||
private final Path<RI> primaryKey;
|
||||
|
||||
protected final E e;
|
||||
@@ -525,7 +569,7 @@ public abstract class AbstractOrientDatabaseService extends AbstractService
|
||||
|
||||
mapper.insert(e, object, row);
|
||||
|
||||
// TODO generate ids
|
||||
// TODO generate unique id
|
||||
row.getDocument().save();
|
||||
rows++;
|
||||
|
||||
|
||||
@@ -16,16 +16,14 @@
|
||||
*/
|
||||
package com.l2jserver.service.database.sql;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
@@ -68,6 +66,9 @@ import com.l2jserver.service.database.dao.UpdateMapper;
|
||||
import com.l2jserver.service.database.ddl.QueryFactory;
|
||||
import com.l2jserver.service.database.ddl.TableFactory;
|
||||
import com.l2jserver.service.database.ddl.struct.Table;
|
||||
import com.l2jserver.util.CSVUtils;
|
||||
import com.l2jserver.util.CSVUtils.CSVMapProcessor;
|
||||
import com.l2jserver.util.QPathUtils;
|
||||
import com.l2jserver.util.factory.CollectionFactory;
|
||||
import com.mysema.query.sql.AbstractSQLQuery;
|
||||
import com.mysema.query.sql.RelationalPath;
|
||||
@@ -237,21 +238,6 @@ public abstract class AbstractSQLDatabaseService extends AbstractService
|
||||
@ConfigurationXPath("/configuration/services/database/jdbc/password")
|
||||
void setPassword(String password);
|
||||
|
||||
/**
|
||||
* @return the update schema state
|
||||
*/
|
||||
@ConfigurationPropertyGetter(defaultValue = "true")
|
||||
@ConfigurationXPath("/configuration/services/database/jdbc/updateSchema")
|
||||
boolean getUpdateSchema();
|
||||
|
||||
/**
|
||||
* @param updateSchema
|
||||
* the new uodate schema state
|
||||
*/
|
||||
@ConfigurationPropertySetter
|
||||
@ConfigurationXPath("/configuration/services/database/jdbc/updateSchema")
|
||||
void setUpdateSchema(boolean updateSchema);
|
||||
|
||||
/**
|
||||
* @return the maximum number of active connections
|
||||
*/
|
||||
@@ -350,18 +336,8 @@ public abstract class AbstractSQLDatabaseService extends AbstractService
|
||||
true);
|
||||
dataSource = new PoolingDataSource(connectionPool);
|
||||
|
||||
if (config.getUpdateSchema()) {
|
||||
try {
|
||||
final Connection conn = dataSource.getConnection();
|
||||
try {
|
||||
ensureDatabaseSchema(conn);
|
||||
} finally {
|
||||
conn.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new ServiceStartException(
|
||||
"Couldn't update database schema", e);
|
||||
}
|
||||
if (config.isAutomaticSchemaUpdateEnabled()) {
|
||||
updateSchemas();
|
||||
}
|
||||
|
||||
for (final Type<?> type : sqlTypes) {
|
||||
@@ -401,51 +377,6 @@ public abstract class AbstractSQLDatabaseService extends AbstractService
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure the database schema is up-to-date with the external database
|
||||
*
|
||||
* @param conn
|
||||
* the connection to be used
|
||||
* @throws SQLException
|
||||
* if any {@link SQLException} occur
|
||||
* @throws IOException
|
||||
* if any {@link IOException} occur
|
||||
*/
|
||||
protected abstract void ensureDatabaseSchema(Connection conn)
|
||||
throws SQLException, IOException;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param conn
|
||||
* the connection to be used
|
||||
* @param table
|
||||
* the {@link RelationalPathBase} table
|
||||
* @return <code>true</code> if a new table was created, <code>false</code>
|
||||
* otherwise.
|
||||
* @throws SQLException
|
||||
* if any {@link SQLException} occur
|
||||
*/
|
||||
protected boolean updateSchema(Connection conn, RelationalPathBase<?> table)
|
||||
throws SQLException {
|
||||
final Table expected = TableFactory.createTable(table);
|
||||
String query = null;
|
||||
boolean create = false;
|
||||
try {
|
||||
final Table current = TableFactory.createTable(conn,
|
||||
engine.getTemplate(), table.getTableName());
|
||||
query = QueryFactory.alterTableQueryUpdate(expected, current,
|
||||
engine.getTemplate());
|
||||
} catch (SQLException e) {
|
||||
// table may not exist
|
||||
query = QueryFactory.createTableQuery(expected,
|
||||
engine.getTemplate());
|
||||
create = true;
|
||||
}
|
||||
if ((engine.getTemplate().supportsAlterTable() && !create) || create)
|
||||
executeSQL(conn, query);
|
||||
return create;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the SQL code in the databases
|
||||
*
|
||||
@@ -462,9 +393,6 @@ public abstract class AbstractSQLDatabaseService extends AbstractService
|
||||
final Statement st = conn.createStatement();
|
||||
try {
|
||||
return st.execute(sql);
|
||||
} catch (SQLException e) {
|
||||
log.warn("Error exectuing query {}", sql);
|
||||
throw e;
|
||||
} finally {
|
||||
st.close();
|
||||
}
|
||||
@@ -472,8 +400,8 @@ public abstract class AbstractSQLDatabaseService extends AbstractService
|
||||
|
||||
@Override
|
||||
public <M extends Model<?>, T extends RelationalPathBase<?>> void importData(
|
||||
java.nio.file.Path path, T entity) throws IOException {
|
||||
Connection conn;
|
||||
java.nio.file.Path path, final T entity) throws IOException {
|
||||
final Connection conn;
|
||||
try {
|
||||
conn = dataSource.getConnection();
|
||||
} catch (SQLException e) {
|
||||
@@ -481,37 +409,30 @@ public abstract class AbstractSQLDatabaseService extends AbstractService
|
||||
}
|
||||
log.info("Importing {} to {}", path, entity);
|
||||
try {
|
||||
BufferedReader reader = Files.newBufferedReader(path,
|
||||
Charset.defaultCharset());
|
||||
final String header[] = reader.readLine().split(",");
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
final String data[] = line.split(",");
|
||||
SQLInsertClause insert = engine.createSQLQueryFactory(conn)
|
||||
.insert(entity);
|
||||
insert.populate(data, new Mapper<Object[]>() {
|
||||
@Override
|
||||
public Map<Path<?>, Object> createMap(
|
||||
RelationalPath<?> relationalPath, Object[] object) {
|
||||
final Map<Path<?>, Object> values = CollectionFactory
|
||||
.newMap();
|
||||
pathFor: for (final Path<?> path : relationalPath
|
||||
.getColumns()) {
|
||||
int i = 0;
|
||||
for (final String headerName : header) {
|
||||
if (path.getMetadata().getExpression()
|
||||
.toString().equals(headerName)) {
|
||||
values.put(path, object[i]);
|
||||
continue pathFor;
|
||||
}
|
||||
i++;
|
||||
CSVUtils.parseCSV(path, new CSVMapProcessor<Long>() {
|
||||
@Override
|
||||
public Long process(final Map<String, String> map) {
|
||||
SQLInsertClause insert = engine.createSQLQueryFactory(conn)
|
||||
.insert(entity);
|
||||
insert.populate(map, new Mapper<Map<String, String>>() {
|
||||
@Override
|
||||
public Map<Path<?>, Object> createMap(
|
||||
RelationalPath<?> relationalPath,
|
||||
Map<String, String> map) {
|
||||
final Map<Path<?>, Object> values = CollectionFactory
|
||||
.newMap();
|
||||
for (final Entry<String, String> entry : map
|
||||
.entrySet()) {
|
||||
final Path<?> path = QPathUtils.getPath(entity,
|
||||
entry.getKey());
|
||||
values.put(path, entry.getValue());
|
||||
}
|
||||
return values;
|
||||
}
|
||||
return values;
|
||||
}
|
||||
});
|
||||
insert.execute();
|
||||
}
|
||||
});
|
||||
return insert.execute();
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
try {
|
||||
conn.close();
|
||||
@@ -520,6 +441,46 @@ public abstract class AbstractSQLDatabaseService extends AbstractService
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateSchema(RelationalPath<?> table) {
|
||||
final Table expected = TableFactory.createTable(table);
|
||||
|
||||
log.info("Updating {} schema definition", table);
|
||||
|
||||
try {
|
||||
final Connection conn = dataSource.getConnection();
|
||||
try {
|
||||
String query = null;
|
||||
boolean create = false;
|
||||
try {
|
||||
final Table current = TableFactory.createTable(conn,
|
||||
engine.getTemplate(), table.getTableName());
|
||||
query = QueryFactory.alterTableQueryUpdate(expected,
|
||||
current, engine.getTemplate());
|
||||
} catch (SQLException e) {
|
||||
// table may not exist
|
||||
query = QueryFactory.createTableQuery(expected,
|
||||
engine.getTemplate());
|
||||
create = true;
|
||||
}
|
||||
if ((engine.getTemplate().supportsAlterTable() && !create)
|
||||
|| create)
|
||||
executeSQL(conn, query);
|
||||
return create;
|
||||
} catch (SQLException e) {
|
||||
throw new DatabaseException(e);
|
||||
} finally {
|
||||
try {
|
||||
conn.close();
|
||||
} catch (SQLException e) {
|
||||
throw new DatabaseException(e);
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new DatabaseException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int transaction(TransactionExecutor executor) {
|
||||
Preconditions.checkNotNull(executor, "executor");
|
||||
|
||||
202
l2jserver2-common/src/main/java/com/l2jserver/util/CSVUtils.java
Normal file
202
l2jserver2-common/src/main/java/com/l2jserver/util/CSVUtils.java
Normal file
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* This file is part of l2jserver2 <l2jserver2.com>.
|
||||
*
|
||||
* l2jserver2 is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* l2jserver2 is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with l2jserver2. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.l2jserver.util;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.l2jserver.util.factory.CollectionFactory;
|
||||
|
||||
/**
|
||||
* This class provices CSV parsing and other utilities
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com">Rogiel</a>
|
||||
*/
|
||||
public class CSVUtils {
|
||||
/**
|
||||
* Parses an CSV files and sends every row to be processed by the
|
||||
* <code>processor</code>.
|
||||
* <p>
|
||||
* <h1>The parsing process</h1>
|
||||
* <p>
|
||||
* The parser reads the first line of the CSV and interprets it as an header
|
||||
* that will be subsequently be passed to the {@link CSVProcessor} as the
|
||||
* <code>header</code> {@link String String[]}.
|
||||
* <p>
|
||||
* Following rows are data rows and are read and passed to the
|
||||
* {@link CSVProcessor} one by one until the end of file.
|
||||
*
|
||||
* @param <R>
|
||||
* the processor return type
|
||||
*
|
||||
* @param reader
|
||||
* the CSV {@link BufferedReader}
|
||||
* @param processor
|
||||
* the processor
|
||||
* @return all processed rows
|
||||
* @throws IOException
|
||||
* if any exception occur while parsing
|
||||
*/
|
||||
public static <R> List<R> parseCSV(BufferedReader reader,
|
||||
CSVProcessor<R> processor) throws IOException {
|
||||
final List<R> results = CollectionFactory.newList();
|
||||
final String header[] = reader.readLine().split(",");
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
results.add(processor.process(header, line.split(",")));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an CSV files and sends every row to be processed by the
|
||||
* <code>processor</code>
|
||||
*
|
||||
* @param <R>
|
||||
* the processor return type
|
||||
*
|
||||
* @param path
|
||||
* the CSV file path
|
||||
* @param processor
|
||||
* the processor
|
||||
* @return all processed rows
|
||||
* @throws IOException
|
||||
* if any exception occur while parsing
|
||||
* @see #parseCSV(BufferedReader, CSVProcessor)
|
||||
* <code>parseCSV(BufferedReader, CSVProcessor)</code> for more details
|
||||
* about how the parsing works
|
||||
*/
|
||||
public static <R> List<R> parseCSV(Path path, CSVProcessor<R> processor)
|
||||
throws IOException {
|
||||
return parseCSV(
|
||||
Files.newBufferedReader(path, Charset.defaultCharset()),
|
||||
processor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an CSV files and sends every row to be processed by the
|
||||
* <code>processor</code>
|
||||
*
|
||||
* @param <R>
|
||||
* the processor return type
|
||||
*
|
||||
* @param file
|
||||
* the CSV file
|
||||
* @param processor
|
||||
* the processor
|
||||
* @return all processed rows
|
||||
* @throws IOException
|
||||
* if any exception occur while parsing
|
||||
*/
|
||||
public static <R> List<R> parseCSV(File file, CSVProcessor<R> processor)
|
||||
throws IOException {
|
||||
return parseCSV(file.toPath(), processor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an CSV files and sends every row to be processed by the
|
||||
* <code>processor</code>
|
||||
*
|
||||
* @param <R>
|
||||
* the processor return type
|
||||
*
|
||||
* @param in
|
||||
* the CSV {@link InputStream}
|
||||
* @param processor
|
||||
* the processor
|
||||
* @return all processed rows
|
||||
* @throws IOException
|
||||
* if any exception occur while parsing
|
||||
*/
|
||||
public static <R> List<R> parseCSV(InputStream in, CSVProcessor<R> processor)
|
||||
throws IOException {
|
||||
return parseCSV(new BufferedReader(new InputStreamReader(in)),
|
||||
processor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an CSV files and sends every row to be processed by the
|
||||
* <code>processor</code>
|
||||
*
|
||||
* @param <R>
|
||||
* the processor return type
|
||||
*
|
||||
* @param reader
|
||||
* the CSV {@link Reader}
|
||||
* @param processor
|
||||
* the processor
|
||||
* @return all processed rows
|
||||
* @throws IOException
|
||||
* if any exception occur while parsing
|
||||
*/
|
||||
public static <R> List<R> parseCSV(Reader reader, CSVProcessor<R> processor)
|
||||
throws IOException {
|
||||
return parseCSV(new BufferedReader(reader), processor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes an single row in an CSV files and returns an processed object.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com">Rogiel</a>
|
||||
* @param <R>
|
||||
* the processed object type
|
||||
*/
|
||||
public interface CSVProcessor<R> {
|
||||
/**
|
||||
* Processes the current row and returns the processed object
|
||||
*
|
||||
* @param header
|
||||
* the CSV header (first row)
|
||||
* @param data
|
||||
* the CSV data
|
||||
* @return the processed object
|
||||
*/
|
||||
R process(String[] header, String[] data);
|
||||
}
|
||||
|
||||
/**
|
||||
* This {@link CSVProcessor} implementations act as an adapter to the String
|
||||
* arrays, providing access to the rows in a more object oriented fashing,
|
||||
* through a {@link Map}.
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com">Rogiel</a>
|
||||
*
|
||||
* @param <R>
|
||||
* the processed object type
|
||||
*/
|
||||
public static abstract class CSVMapProcessor<R> implements CSVProcessor<R> {
|
||||
@Override
|
||||
public final R process(String[] header, String[] data) {
|
||||
final Map<String, String> map = CollectionFactory.newMap();
|
||||
for (int i = 0; i < header.length; i++) {
|
||||
map.put(header[i], data[i]);
|
||||
}
|
||||
return process(map);
|
||||
}
|
||||
|
||||
public abstract R process(Map<String, String> map);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* This file is part of l2jserver2 <l2jserver2.com>.
|
||||
*
|
||||
* l2jserver2 is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* l2jserver2 is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with l2jserver2. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.l2jserver.util;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import com.l2jserver.service.database.ddl.annotation.ColumnAutoIncrement;
|
||||
import com.l2jserver.service.database.ddl.annotation.ColumnDefault;
|
||||
import com.l2jserver.service.database.ddl.annotation.ColumnNullable;
|
||||
import com.l2jserver.service.database.ddl.annotation.ColumnSize;
|
||||
import com.l2jserver.util.transformer.Transformer;
|
||||
import com.l2jserver.util.transformer.TransformerFactory;
|
||||
import com.mysema.query.sql.RelationalPath;
|
||||
import com.mysema.query.types.Path;
|
||||
|
||||
/**
|
||||
* Class serving several utilities for {@link Path} and {@link RelationalPath}
|
||||
*
|
||||
* @author <a href="http://www.rogiel.com">Rogiel</a>
|
||||
*/
|
||||
public class QPathUtils {
|
||||
/**
|
||||
* Returns the {@link Path} represented by <code>name</code> within the
|
||||
* given {@link RelationalPath}
|
||||
*
|
||||
* @param relationalPath
|
||||
* the {@link RelationalPath}
|
||||
* @param name
|
||||
* the {@link Path} name
|
||||
* @return the {@link Path} instance, if existent
|
||||
*/
|
||||
public static Path<?> getPath(RelationalPath<?> relationalPath, String name) {
|
||||
for (final Path<?> path : relationalPath.getColumns()) {
|
||||
if (path.getMetadata().getExpression().toString().equals(name))
|
||||
return path;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param path
|
||||
* the {@link Path} to be searched in its root entity
|
||||
* @return the {@link Field} holding <code>path</code>
|
||||
*/
|
||||
public static Field getReflectionField(Path<?> path) {
|
||||
return ClassUtils.getFieldWithValue(path.getMetadata().getParent(), path);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param relationalPath
|
||||
* the {@link RelationalPath}
|
||||
* @param pathName
|
||||
* the {@link Path} name
|
||||
* @return the {@link Field} holding <code>pathName</code>
|
||||
* @see #getPath(RelationalPath, String)
|
||||
*/
|
||||
public static Field getRefelctionField(RelationalPath<?> relationalPath,
|
||||
String pathName) {
|
||||
return ClassUtils.getFieldWithValue(relationalPath,
|
||||
getPath(relationalPath, pathName));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param path
|
||||
* the {@link Path}
|
||||
* @return the column maximum size
|
||||
*/
|
||||
public static int getColumnSize(Path<?> path) {
|
||||
final Field field = getReflectionField(path);
|
||||
if(field == null)
|
||||
return 0;
|
||||
final ColumnSize size = field.getAnnotation(ColumnSize.class);
|
||||
if (size != null)
|
||||
return size.value();
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param path
|
||||
* the {@link Path}
|
||||
* @return whether the column is auto incrementable or not
|
||||
*/
|
||||
public static boolean isAutoIncrementable(Path<?> path) {
|
||||
final Field field = getReflectionField(path);
|
||||
if(field == null)
|
||||
return false;
|
||||
final ColumnAutoIncrement autoInc = field
|
||||
.getAnnotation(ColumnAutoIncrement.class);
|
||||
if (autoInc != null)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param path
|
||||
* the {@link Path}
|
||||
* @return whether the column can be <code>null</code>
|
||||
*/
|
||||
public static boolean isNullable(Path<?> path) {
|
||||
final Field field = getReflectionField(path);
|
||||
if(field == null)
|
||||
return false;
|
||||
final ColumnNullable nullable = field
|
||||
.getAnnotation(ColumnNullable.class);
|
||||
if (nullable == null) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param path
|
||||
* the {@link Path}
|
||||
* @return <code>true</code> if the column has an default value
|
||||
*/
|
||||
public static boolean hasDefaultValue(Path<?> path) {
|
||||
final Field field = getReflectionField(path);
|
||||
if(field == null)
|
||||
return false;
|
||||
final ColumnDefault def = field.getAnnotation(ColumnDefault.class);
|
||||
if (def == null) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param path
|
||||
* the {@link Path}
|
||||
* @return the column default value
|
||||
*/
|
||||
public static String getDefaultUntransformedValue(Path<?> path) {
|
||||
final Field field = getReflectionField(path);
|
||||
if(field == null)
|
||||
return null;
|
||||
final ColumnDefault def = field.getAnnotation(ColumnDefault.class);
|
||||
if (def != null)
|
||||
return def.value();
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param <T>
|
||||
* the column return type
|
||||
* @param path
|
||||
* the {@link Path}
|
||||
* @return the column default value (already transformed)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T getDefaultValue(Path<T> path) {
|
||||
@SuppressWarnings("rawtypes")
|
||||
final Transformer transformer = TransformerFactory.getTransfromer(path
|
||||
.getType());
|
||||
return (T) transformer.untransform(path.getType(),
|
||||
getDefaultUntransformedValue(path));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user