From d363c30c5c84707cbc40179d6afd99bd6f564af5 Mon Sep 17 00:00:00 2001 From: Rogiel Date: Mon, 26 Dec 2011 01:08:27 -0200 Subject: [PATCH] Implements better schema management --- .../service/database/DatabaseService.java | 34 ++- .../service/database/ddl/TableFactory.java | 20 +- .../AbstractOrientDatabaseService.java | 134 ++++++++---- .../sql/AbstractSQLDatabaseService.java | 179 ++++++---------- .../java/com/l2jserver/util/CSVUtils.java | 202 ++++++++++++++++++ .../java/com/l2jserver/util/QPathUtils.java | 173 +++++++++++++++ l2jserver2-gameserver/config/config.xml | 16 +- .../distribution/global/config/config.xml | 21 +- .../java/com/l2jserver/GameServerModule.java | 4 +- .../com/l2jserver/service/ServiceModule.java | 4 +- .../GameServerJDBCDatabaseService.java | 27 +-- .../GameServerOrientDatabaseService.java | 21 +- .../service/database/model/QActorSkill.java | 7 +- .../LoginServerSQLDatabaseService.java | 10 +- 14 files changed, 635 insertions(+), 217 deletions(-) create mode 100644 l2jserver2-common/src/main/java/com/l2jserver/util/CSVUtils.java create mode 100644 l2jserver2-common/src/main/java/com/l2jserver/util/QPathUtils.java diff --git a/l2jserver2-common/src/main/java/com/l2jserver/service/database/DatabaseService.java b/l2jserver2-common/src/main/java/com/l2jserver/service/database/DatabaseService.java index 102279c0e..75d4d1563 100644 --- a/l2jserver2-common/src/main/java/com/l2jserver/service/database/DatabaseService.java +++ b/l2jserver2-common/src/main/java/com/l2jserver/service/database/DatabaseService.java @@ -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 */ , T extends RelationalPathBase> void importData( - Path path, T entity) - throws IOException; + Path path, T entity) throws IOException; + + /** + * Updates the given schema 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 diff --git a/l2jserver2-common/src/main/java/com/l2jserver/service/database/ddl/TableFactory.java b/l2jserver2-common/src/main/java/com/l2jserver/service/database/ddl/TableFactory.java index eec2a74f7..a1c35863b 100644 --- a/l2jserver2-common/src/main/java/com/l2jserver/service/database/ddl/TableFactory.java +++ b/l2jserver2-common/src/main/java/com/l2jserver/service/database/ddl/TableFactory.java @@ -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 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 createFKs(RelationalPathBase tablePath, + private static List createFKs(RelationalPath tablePath, Map columns) { final List 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 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); diff --git a/l2jserver2-common/src/main/java/com/l2jserver/service/database/orientdb/AbstractOrientDatabaseService.java b/l2jserver2-common/src/main/java/com/l2jserver/service/database/orientdb/AbstractOrientDatabaseService.java index c3b53ddb0..85bd447fa 100644 --- a/l2jserver2-common/src/main/java/com/l2jserver/service/database/orientdb/AbstractOrientDatabaseService.java +++ b/l2jserver2-common/src/main/java/com/l2jserver/service/database/orientdb/AbstractOrientDatabaseService.java @@ -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 transaction = new ThreadLocal(); /** * 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 query(Query 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 , 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() { + @Override + public Object process(Map map) { + final ODocument document = new ODocument(database, entity + .getTableName()); + for (final Entry 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 mapper; private final Iterator iterator; @SuppressWarnings("unused") - // FIXME implement id generation private final Path 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++; diff --git a/l2jserver2-common/src/main/java/com/l2jserver/service/database/sql/AbstractSQLDatabaseService.java b/l2jserver2-common/src/main/java/com/l2jserver/service/database/sql/AbstractSQLDatabaseService.java index 22984c12f..2d9a7f9e0 100644 --- a/l2jserver2-common/src/main/java/com/l2jserver/service/database/sql/AbstractSQLDatabaseService.java +++ b/l2jserver2-common/src/main/java/com/l2jserver/service/database/sql/AbstractSQLDatabaseService.java @@ -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 true if a new table was created, false - * 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 , 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() { - @Override - public Map, Object> createMap( - RelationalPath relationalPath, Object[] object) { - final Map, 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() { + @Override + public Long process(final Map map) { + SQLInsertClause insert = engine.createSQLQueryFactory(conn) + .insert(entity); + insert.populate(map, new Mapper>() { + @Override + public Map, Object> createMap( + RelationalPath relationalPath, + Map map) { + final Map, Object> values = CollectionFactory + .newMap(); + for (final Entry 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"); diff --git a/l2jserver2-common/src/main/java/com/l2jserver/util/CSVUtils.java b/l2jserver2-common/src/main/java/com/l2jserver/util/CSVUtils.java new file mode 100644 index 000000000..67b603f38 --- /dev/null +++ b/l2jserver2-common/src/main/java/com/l2jserver/util/CSVUtils.java @@ -0,0 +1,202 @@ +/* + * This file is part of l2jserver2 . + * + * 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 . + */ +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 Rogiel + */ +public class CSVUtils { + /** + * Parses an CSV files and sends every row to be processed by the + * processor. + *

+ *

The parsing process

+ *

+ * 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 + * header {@link String String[]}. + *

+ * Following rows are data rows and are read and passed to the + * {@link CSVProcessor} one by one until the end of file. + * + * @param + * 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 List parseCSV(BufferedReader reader, + CSVProcessor processor) throws IOException { + final List 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 + * processor + * + * @param + * 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) + * parseCSV(BufferedReader, CSVProcessor) for more details + * about how the parsing works + */ + public static List parseCSV(Path path, CSVProcessor processor) + throws IOException { + return parseCSV( + Files.newBufferedReader(path, Charset.defaultCharset()), + processor); + } + + /** + * Parses an CSV files and sends every row to be processed by the + * processor + * + * @param + * 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 List parseCSV(File file, CSVProcessor processor) + throws IOException { + return parseCSV(file.toPath(), processor); + } + + /** + * Parses an CSV files and sends every row to be processed by the + * processor + * + * @param + * 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 List parseCSV(InputStream in, CSVProcessor processor) + throws IOException { + return parseCSV(new BufferedReader(new InputStreamReader(in)), + processor); + } + + /** + * Parses an CSV files and sends every row to be processed by the + * processor + * + * @param + * 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 List parseCSV(Reader reader, CSVProcessor processor) + throws IOException { + return parseCSV(new BufferedReader(reader), processor); + } + + /** + * Processes an single row in an CSV files and returns an processed object. + * + * @author Rogiel + * @param + * the processed object type + */ + public interface CSVProcessor { + /** + * 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 Rogiel + * + * @param + * the processed object type + */ + public static abstract class CSVMapProcessor implements CSVProcessor { + @Override + public final R process(String[] header, String[] data) { + final Map 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 map); + } +} diff --git a/l2jserver2-common/src/main/java/com/l2jserver/util/QPathUtils.java b/l2jserver2-common/src/main/java/com/l2jserver/util/QPathUtils.java new file mode 100644 index 000000000..d99276b62 --- /dev/null +++ b/l2jserver2-common/src/main/java/com/l2jserver/util/QPathUtils.java @@ -0,0 +1,173 @@ +/* + * This file is part of l2jserver2 . + * + * 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 . + */ +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 Rogiel + */ +public class QPathUtils { + /** + * Returns the {@link Path} represented by name 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 path + */ + 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 pathName + * @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 null + */ + 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 true 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 + * the column return type + * @param path + * the {@link Path} + * @return the column default value (already transformed) + */ + @SuppressWarnings("unchecked") + public static T getDefaultValue(Path path) { + @SuppressWarnings("rawtypes") + final Transformer transformer = TransformerFactory.getTransfromer(path + .getType()); + return (T) transformer.untransform(path.getType(), + getDefaultUntransformedValue(path)); + } +} diff --git a/l2jserver2-gameserver/config/config.xml b/l2jserver2-gameserver/config/config.xml index 2cd249f53..f2fc211c6 100644 --- a/l2jserver2-gameserver/config/config.xml +++ b/l2jserver2-gameserver/config/config.xml @@ -19,11 +19,22 @@ not sure on the usage of any parameter, read the "Configuration" section in wiki article about DatabaseService. --> + + true + + + local:data/database + admin + admin + + jdbc:mysql://localhost/l2jserver2 @@ -32,11 +43,6 @@ com.l2jserver.service.database.sql.MySQLDatabaseEngine - - true - l2j diff --git a/l2jserver2-gameserver/distribution/global/config/config.xml b/l2jserver2-gameserver/distribution/global/config/config.xml index 365627f86..f2fc211c6 100644 --- a/l2jserver2-gameserver/distribution/global/config/config.xml +++ b/l2jserver2-gameserver/distribution/global/config/config.xml @@ -19,6 +19,22 @@ not sure on the usage of any parameter, read the "Configuration" section in wiki article about DatabaseService. --> + + true + + + + + local:data/database + + admin + + admin + + + jdbc:mysql://localhost/l2jserver2 @@ -27,11 +43,6 @@ com.l2jserver.service.database.sql.MySQLDatabaseEngine - - true - l2j diff --git a/l2jserver2-gameserver/src/main/java/com/l2jserver/GameServerModule.java b/l2jserver2-gameserver/src/main/java/com/l2jserver/GameServerModule.java index 2305e5a18..a156b61ca 100644 --- a/l2jserver2-gameserver/src/main/java/com/l2jserver/GameServerModule.java +++ b/l2jserver2-gameserver/src/main/java/com/l2jserver/GameServerModule.java @@ -20,7 +20,7 @@ import com.google.inject.AbstractModule; import com.google.inject.Module; import com.l2jserver.model.id.provider.IDProviderModule; import com.l2jserver.service.ServiceModule; -import com.l2jserver.service.database.JDBCDAOModule; +import com.l2jserver.service.database.OrientDBDAOModule; /** * The game server Google Guice {@link Module}. @@ -32,6 +32,6 @@ public class GameServerModule extends AbstractModule { protected void configure() { install(new ServiceModule()); install(new IDProviderModule()); - install(new JDBCDAOModule()); + install(new OrientDBDAOModule()); } } diff --git a/l2jserver2-gameserver/src/main/java/com/l2jserver/service/ServiceModule.java b/l2jserver2-gameserver/src/main/java/com/l2jserver/service/ServiceModule.java index 77833bd03..f679a5918 100644 --- a/l2jserver2-gameserver/src/main/java/com/l2jserver/service/ServiceModule.java +++ b/l2jserver2-gameserver/src/main/java/com/l2jserver/service/ServiceModule.java @@ -30,7 +30,7 @@ import com.l2jserver.service.core.threading.ThreadServiceImpl; import com.l2jserver.service.core.vfs.Java7VFSService; import com.l2jserver.service.core.vfs.VFSService; import com.l2jserver.service.database.DatabaseService; -import com.l2jserver.service.database.GameServerJDBCDatabaseService; +import com.l2jserver.service.database.GameServerOrientDatabaseService; import com.l2jserver.service.game.AttackService; import com.l2jserver.service.game.AttackServiceImpl; import com.l2jserver.service.game.admin.AdministratorService; @@ -89,7 +89,7 @@ public class ServiceModule extends AbstractModule { bind(CacheService.class).to(SoftCacheService.class) .in(Scopes.SINGLETON); - bind(DatabaseService.class).to(GameServerJDBCDatabaseService.class) + bind(DatabaseService.class).to(GameServerOrientDatabaseService.class) .in(Scopes.SINGLETON); bind(WorldIDService.class).to(CachedWorldIDService.class).in( Scopes.SINGLETON); diff --git a/l2jserver2-gameserver/src/main/java/com/l2jserver/service/database/GameServerJDBCDatabaseService.java b/l2jserver2-gameserver/src/main/java/com/l2jserver/service/database/GameServerJDBCDatabaseService.java index e82852e6e..6c6fe4fa8 100644 --- a/l2jserver2-gameserver/src/main/java/com/l2jserver/service/database/GameServerJDBCDatabaseService.java +++ b/l2jserver2-gameserver/src/main/java/com/l2jserver/service/database/GameServerJDBCDatabaseService.java @@ -17,8 +17,6 @@ package com.l2jserver.service.database; import java.io.IOException; -import java.sql.Connection; -import java.sql.SQLException; import com.google.inject.Inject; import com.l2jserver.model.game.CharacterShortcut.ShortcutType; @@ -112,17 +110,20 @@ public class GameServerJDBCDatabaseService extends AbstractSQLDatabaseService } @Override - protected void ensureDatabaseSchema(Connection conn) throws SQLException, - IOException { - updateSchema(conn, QActorSkill.actorSkill); - updateSchema(conn, QCharacter.character); - updateSchema(conn, QCharacterFriend.characterFriend); - updateSchema(conn, QCharacterShortcut.characterShortcut); - updateSchema(conn, QClan.clan); - updateSchema(conn, QItem.item); - updateSchema(conn, QLogChat.logChat); - if (updateSchema(conn, QNPC.npc)) { - importData(vfsService.resolve("data/static/npc.csv"), QNPC.npc); + public void updateSchemas() { + updateSchema(QActorSkill.actorSkill); + updateSchema(QCharacter.character); + updateSchema(QCharacterFriend.characterFriend); + updateSchema(QCharacterShortcut.characterShortcut); + updateSchema(QClan.clan); + updateSchema(QItem.item); + updateSchema(QLogChat.logChat); + if (updateSchema(QNPC.npc)) { + try { + importData(vfsService.resolve("data/static/npc.csv"), QNPC.npc); + } catch (IOException e) { + throw new DatabaseException(e); + } } } } diff --git a/l2jserver2-gameserver/src/main/java/com/l2jserver/service/database/GameServerOrientDatabaseService.java b/l2jserver2-gameserver/src/main/java/com/l2jserver/service/database/GameServerOrientDatabaseService.java index d2a0943d8..c83fe875d 100644 --- a/l2jserver2-gameserver/src/main/java/com/l2jserver/service/database/GameServerOrientDatabaseService.java +++ b/l2jserver2-gameserver/src/main/java/com/l2jserver/service/database/GameServerOrientDatabaseService.java @@ -82,20 +82,19 @@ public class GameServerOrientDatabaseService extends } @Override - protected void ensureDatabaseSchema() { - createSchema(QActorSkill.actorSkill); - createSchema(QCharacter.character); - createSchema(QCharacterFriend.characterFriend); - createSchema(QCharacterShortcut.characterShortcut); - createSchema(QClan.clan); - createSchema(QItem.item); - createSchema(QLogChat.logChat); - if (createSchema(QNPC.npc)) { + public void updateSchemas() { + updateSchema(QActorSkill.actorSkill); + updateSchema(QCharacter.character); + updateSchema(QCharacterFriend.characterFriend); + updateSchema(QCharacterShortcut.characterShortcut); + updateSchema(QClan.clan); + updateSchema(QItem.item); + updateSchema(QLogChat.logChat); + if (updateSchema(QNPC.npc)) { try { importData(Paths.get("data/static/npc.csv"), QNPC.npc); } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + throw new DatabaseException(e); } } } diff --git a/l2jserver2-gameserver/src/main/java/com/l2jserver/service/database/model/QActorSkill.java b/l2jserver2-gameserver/src/main/java/com/l2jserver/service/database/model/QActorSkill.java index 421974766..18c7f8aab 100644 --- a/l2jserver2-gameserver/src/main/java/com/l2jserver/service/database/model/QActorSkill.java +++ b/l2jserver2-gameserver/src/main/java/com/l2jserver/service/database/model/QActorSkill.java @@ -23,15 +23,14 @@ public class QActorSkill extends RelationalPathBase { @ColumnSize(10) public final NumberPath actorId = createNumber("actor_id", Integer.class); + @ColumnSize(6) + public final NumberPath skillId = createNumber("skill_id", + Integer.class); @ColumnSize(4) public final NumberPath level = createNumber("level", Integer.class); - @ColumnSize(6) - public final NumberPath skillId = createNumber("skill_id", - Integer.class); - public final PrimaryKey primary = createPrimaryKey(actorId, skillId); public QActorSkill(String variable) { diff --git a/l2jserver2-loginserver/src/main/java/com/l2jserver/service/database/LoginServerSQLDatabaseService.java b/l2jserver2-loginserver/src/main/java/com/l2jserver/service/database/LoginServerSQLDatabaseService.java index 5ff212565..c33862427 100644 --- a/l2jserver2-loginserver/src/main/java/com/l2jserver/service/database/LoginServerSQLDatabaseService.java +++ b/l2jserver2-loginserver/src/main/java/com/l2jserver/service/database/LoginServerSQLDatabaseService.java @@ -16,21 +16,13 @@ */ package com.l2jserver.service.database; -import java.io.IOException; -import java.nio.file.Path; -import java.sql.Connection; -import java.sql.SQLException; - -import com.l2jserver.model.Model; import com.l2jserver.service.AbstractService.Depends; import com.l2jserver.service.cache.CacheService; import com.l2jserver.service.configuration.ConfigurationService; import com.l2jserver.service.core.LoggingService; import com.l2jserver.service.core.threading.ThreadService; import com.l2jserver.service.core.vfs.VFSService; -import com.l2jserver.service.database.dao.SelectMapper; import com.l2jserver.service.database.sql.AbstractSQLDatabaseService; -import com.mysema.query.sql.RelationalPathBase; /** * This is an implementation of {@link DatabaseService} that provides an layer @@ -80,6 +72,6 @@ public class LoginServerSQLDatabaseService extends AbstractSQLDatabaseService } @Override - protected void ensureDatabaseSchema(Connection conn) throws SQLException { + public void updateSchemas() { } }