diff --git a/data/.gitignore b/data/.gitignore index 3303592d0..6ae67cfe5 100644 --- a/data/.gitignore +++ b/data/.gitignore @@ -1,3 +1,4 @@ /cache /pathing.db /database.h2.db +/database \ No newline at end of file diff --git a/pom.xml b/pom.xml index e3c654f85..621f6dd8b 100644 --- a/pom.xml +++ b/pom.xml @@ -93,9 +93,18 @@ src/assembly/distribution-mysql5-bin.xml src/assembly/distribution-h2-bin.xml + src/assembly/distribution-orientdb-bin.xml src/assembly/distribution-src.xml + + + package + + assembly + + + @@ -158,22 +167,27 @@ mysql mysql-connector-java 5.1.16 - jar - runtime + compile com.h2database h2 1.3.155 - jar - runtime + compile commons-dbcp commons-dbcp 1.4 jar - runtime + compile + + + + com.orientechnologies + orientdb-core + 1.0rc5 + compile @@ -234,7 +248,6 @@ jar runtime - @@ -253,8 +266,13 @@ default - EclipseLink Repo - http://www.eclipse.org/downloads/download.php?r=1&nf=1&file=/rt/eclipselink/maven.repo + orientechnologies-repository + Orient Technologies Maven2 Repository + http://www.orientechnologies.com/listing/m2 + + true + always + diff --git a/src/assembly/distribution-h2-bin.xml b/src/assembly/distribution-h2-bin.xml index b65c20d1e..59b6145fa 100644 --- a/src/assembly/distribution-h2-bin.xml +++ b/src/assembly/distribution-h2-bin.xml @@ -42,12 +42,13 @@ /libs - true + false false runtime - mysql:mysql-connector-java + mysql:* + com.orientechnologies:* - \ No newline at end of file + diff --git a/src/assembly/distribution-mysql5-bin.xml b/src/assembly/distribution-mysql5-bin.xml index 072becec2..4356408ab 100644 --- a/src/assembly/distribution-mysql5-bin.xml +++ b/src/assembly/distribution-mysql5-bin.xml @@ -42,12 +42,13 @@ /libs - true + false false runtime - com.h2database:h2 + com.h2database:* + com.orientechnologies:* - \ No newline at end of file + diff --git a/src/assembly/distribution-orientdb-bin.xml b/src/assembly/distribution-orientdb-bin.xml new file mode 100644 index 000000000..97479bfae --- /dev/null +++ b/src/assembly/distribution-orientdb-bin.xml @@ -0,0 +1,51 @@ + + orientdb-bin + + + + zip + + + + + ${project.basedir} + / + + data/** + + + .gitignore + data/cache/** + data/pathing.db + data/database.h2.* + + + + ${project.basedir}/dist + / + + + ${project.build.directory} + / + + ${project.artifactId}-${project.version}.jar + + + + + + /libs + false + false + runtime + + mysql:* + com.h2database:* + commons-dbcp:* + + + + \ No newline at end of file diff --git a/src/assembly/distribution-src.xml b/src/assembly/distribution-src.xml index 525a6de14..f28a36822 100644 --- a/src/assembly/distribution-src.xml +++ b/src/assembly/distribution-src.xml @@ -26,9 +26,9 @@ /libs - true + false false runtime - \ No newline at end of file + diff --git a/src/main/java/com/l2jserver/service/database/OrientDatabaseService.java b/src/main/java/com/l2jserver/service/database/OrientDatabaseService.java new file mode 100644 index 000000000..aeb2d1fa3 --- /dev/null +++ b/src/main/java/com/l2jserver/service/database/OrientDatabaseService.java @@ -0,0 +1,726 @@ +/* + * This file is part of l2jserver . + * + * l2jserver 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. + * + * l2jserver 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 l2jserver. If not, see . + */ +package com.l2jserver.service.database; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Preconditions; +import com.google.inject.Guice; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.l2jserver.model.Model; +import com.l2jserver.model.Model.ObjectDesire; +import com.l2jserver.model.dao.CharacterDAO; +import com.l2jserver.model.dao.ClanDAO; +import com.l2jserver.model.dao.ItemDAO; +import com.l2jserver.model.dao.NPCDAO; +import com.l2jserver.model.dao.PetDAO; +import com.l2jserver.model.id.ID; +import com.l2jserver.model.id.object.allocator.IDAllocator; +import com.l2jserver.model.world.Clan; +import com.l2jserver.model.world.Item; +import com.l2jserver.model.world.L2Character; +import com.l2jserver.model.world.NPC; +import com.l2jserver.model.world.Pet; +import com.l2jserver.service.AbstractService; +import com.l2jserver.service.AbstractService.Depends; +import com.l2jserver.service.ServiceStartException; +import com.l2jserver.service.ServiceStopException; +import com.l2jserver.service.cache.Cache; +import com.l2jserver.service.cache.CacheService; +import com.l2jserver.service.configuration.ConfigurationService; +import com.l2jserver.service.configuration.ProxyConfigurationService.ConfigurationPropertiesKey; +import com.l2jserver.service.configuration.XMLConfigurationService.ConfigurationXPath; +import com.l2jserver.service.core.LoggingService; +import com.l2jserver.service.core.threading.ScheduledAsyncFuture; +import com.l2jserver.service.core.threading.ThreadService; +import com.l2jserver.service.game.template.TemplateService; +import com.l2jserver.util.ArrayIterator; +import com.l2jserver.util.ClassUtils; +import com.l2jserver.util.factory.CollectionFactory; +import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; +import com.orientechnologies.orient.core.query.nativ.ONativeSynchQuery; +import com.orientechnologies.orient.core.query.nativ.OQueryContextNativeSchema; +import com.orientechnologies.orient.core.record.impl.ODocument; + +/** + * This is an implementation of {@link DatabaseService} that provides an layer + * to JDBC. + * + *

Internal specification

The {@link Query} object

+ * + * If you wish to implement a new {@link DataAccessObject} you should try not + * use {@link Query} object directly because it only provides low level access + * to the JDBC architecture. Instead, you could use an specialized class, like + * {@link InsertUpdateQuery}, {@link SelectListQuery} or + * {@link SelectSingleQuery}. If you do need low level access, feel free to use + * the {@link Query} class directly. + * + *

The {@link Mapper} object

+ * + * The {@link Mapper} object maps an JDBC {@link ResultSet} into an Java + * {@link Object}. All {@link Model} objects support {@link CachedMapper} that + * will cache result based on its {@link ID} and always use the same object with + * the same {@link ID}. + * + * @author Rogiel + */ +@Depends({ LoggingService.class, CacheService.class, + ConfigurationService.class, TemplateService.class, ThreadService.class }) +public class OrientDatabaseService extends AbstractService implements + DatabaseService { + /** + * The configuration object + */ + private final OrientDatabaseConfiguration config; + /** + * The logger + */ + private final Logger log = LoggerFactory + .getLogger(OrientDatabaseService.class); + + /** + * The Google Guice {@link Injector}. It is used to get DAO instances. + */ + private final Injector injector; + + /** + * The cache service + */ + private final CacheService cacheService; + /** + * The thread service + */ + private final ThreadService threadService; + + private ODatabaseDocumentTx database; + + /** + * An cache object + */ + private Cache> objectCache; + /** + * Future for the auto-save task. Each object that has changed is auto saved + * every 1 minute. + */ + private ScheduledAsyncFuture autoSaveFuture; + + /** + * Configuration interface for {@link OrientDatabaseService}. + * + * @author Rogiel + */ + public interface OrientDatabaseConfiguration extends DatabaseConfiguration { + /** + * @return the orientdb url + */ + @ConfigurationPropertyGetter(defaultValue = "file:data/database") + @ConfigurationPropertiesKey("orientdb.url") + @ConfigurationXPath("/configuration/services/database/orientdb/url") + String getUrl(); + + /** + * @param url + * the new orientdb url + */ + @ConfigurationPropertySetter + @ConfigurationPropertiesKey("orientdb.url") + @ConfigurationXPath("/configuration/services/database/orientdb/url") + void setUrl(String url); + + /** + * @return the orientdb database username + */ + @ConfigurationPropertyGetter(defaultValue = "l2j") + @ConfigurationPropertiesKey("orientdb.username") + @ConfigurationXPath("/configuration/services/database/orientdb/username") + String getUsername(); + + /** + * @param username + * the orientdb database username + */ + @ConfigurationPropertySetter + @ConfigurationPropertiesKey("orientdb.username") + @ConfigurationXPath("/configuration/services/database/orientdb/username") + void setUsername(String username); + + /** + * @return the orientdb database password + */ + @ConfigurationPropertyGetter(defaultValue = "changeme") + @ConfigurationPropertiesKey("orientdb.password") + @ConfigurationXPath("/configuration/services/database/orientdb/password") + String getPassword(); + + /** + * @param password + * the jdbc database password + */ + @ConfigurationPropertySetter + @ConfigurationPropertiesKey("jdbc.password") + @ConfigurationXPath("/configuration/services/database/jdbc/password") + void setPassword(String password); + } + + /** + * @param configService + * the configuration service + * @param injector + * the {@link Guice} {@link Injector} + * @param cacheService + * the cache service + * @param threadService + * the thread service + */ + @Inject + public OrientDatabaseService(ConfigurationService configService, + Injector injector, CacheService cacheService, + ThreadService threadService) { + config = configService.get(OrientDatabaseConfiguration.class); + this.injector = injector; + this.cacheService = cacheService; + this.threadService = threadService; + } + + @Override + protected void doStart() throws ServiceStartException { + database = new ODatabaseDocumentTx(config.getUrl()); + if (!database.exists()) { + database.create(); + } else { + database.open(config.getUsername(), config.getPassword()); + } + + // cache must be large enough for all world objects, to avoid + // duplication... this would endanger non-persistent states + objectCache = cacheService.createEternalCache("database-service", + IDAllocator.ALLOCABLE_IDS); + + // start the auto save task + autoSaveFuture = threadService.async(60, TimeUnit.SECONDS, 60, + new Runnable() { + @Override + public void run() { + log.debug("Auto save task started"); + int objects = 0; + for (final Model object : objectCache) { + @SuppressWarnings("unchecked") + final DataAccessObject, ?> dao = getDAO(object + .getClass()); + if (dao.save(object)) { + objects++; + } + } + log.info( + "{} objects have been saved by the auto save task", + objects); + } + }); + } + + @Override + @SuppressWarnings({ "unchecked", "rawtypes" }) + public , I extends ID> DataAccessObject getDAO( + Class model) { + if (ClassUtils.isSubclass(model, L2Character.class)) { + return (DataAccessObject) injector.getInstance(CharacterDAO.class); + } else if (ClassUtils.isSubclass(model, Clan.class)) { + return (DataAccessObject) injector.getInstance(ClanDAO.class); + } else if (ClassUtils.isSubclass(model, Item.class)) { + return (DataAccessObject) injector.getInstance(ItemDAO.class); + } else if (ClassUtils.isSubclass(model, NPC.class)) { + return (DataAccessObject) injector.getInstance(NPCDAO.class); + } else if (ClassUtils.isSubclass(model, Pet.class)) { + return (DataAccessObject) injector.getInstance(PetDAO.class); + } + return null; + } + + /** + * Executes an query in the database. + * + * @param + * the query return type + * @param query + * the query + * @return an instance of T + */ + public T query(Query query) { + Preconditions.checkNotNull(query, "query"); + log.debug("Executing query {} with {}", query, database); + try { + return query.query(database); + } catch (SQLException e) { + log.error("Database error", e); + return null; + } + } + + /** + * Checks for the cached version of the object + * + * @param id + * the object ID + * @return the cached version, if any + */ + public Object getCachedObject(Object id) { + Preconditions.checkNotNull(id, "id"); + log.debug("Fetching cached object {}", id); + return objectCache.get(id); + } + + /** + * Checks for the cached version of the object + * + * @param id + * the object ID + * @return true if has an cached version, + */ + public boolean hasCachedObject(Object id) { + Preconditions.checkNotNull(id, "id"); + log.debug("Locating cached object {}", id); + return objectCache.contains(id); + } + + /** + * Updates an cache object + * + * @param id + * the cache key + * @param value + * the model value + */ + public void updateCache(ID id, Model value) { + Preconditions.checkNotNull(id, "key"); + Preconditions.checkNotNull(value, "value"); + log.debug("Updating cached object {} with {}", id, value); + objectCache.put(id, value); + } + + /** + * Removes an cached object + * + * @param id + * the object id + */ + public void removeCache(Object id) { + Preconditions.checkNotNull(id, "key"); + log.debug("Removing cached object {}", id); + objectCache.remove(id); + } + + @Override + protected void doStop() throws ServiceStopException { + autoSaveFuture.cancel(true); + autoSaveFuture = null; + cacheService.dispose(objectCache); + objectCache = null; + database.close(); + database = null; + } + + /** + * The query interface. The query will receive an connection an will be + * executed. The can return return a value if required. + * + * @author Rogiel + * + * @param + * the return type + */ + public interface Query { + /** + * Execute the query in conn + * + * @param database + * the database connection + * @return the query return value + * @throws SQLException + * if any SQL error occur + */ + R query(ODatabaseDocumentTx database) throws SQLException; + } + + /** + * This query is used for the following statements: + *
    + *
  • INSERT INTO
  • + *
  • UPDATE
  • + *
  • DELETE FROM
  • + *
+ * + * @author Rogiel + * + * @param + * the query return type + */ + public static abstract class InsertUpdateQuery implements Query { + /** + * The logger + */ + private final Logger log = LoggerFactory + .getLogger(InsertUpdateQuery.class); + + /** + * The iterator + */ + private final Iterator iterator; + + /** + * Creates a new query for objects + * + * @param objects + * the object list + */ + public InsertUpdateQuery(T... objects) { + this(new ArrayIterator(objects)); + } + + /** + * Create a new query for objects in iterator + * + * @param iterator + * the object iterator + */ + public InsertUpdateQuery(Iterator iterator) { + this.iterator = iterator; + } + + @Override + public Integer query(ODatabaseDocumentTx database) throws SQLException { + Preconditions.checkNotNull(database, "database"); + + log.debug("Starting INSERT/UPDATE query execution"); + + int rows = 0; + while (iterator.hasNext()) { + final T object = iterator.next(); + final ONativeSynchQuery> query = createQuery( + database, object); + final ODocument document; + if (query != null) { + List docs = database.query(query); + if (docs.size() >= 1) { + document = update(docs.get(0), object); + } else { + continue; + } + } else { + document = insert(new ODocument(), object); + } + if (document != null) + database.save(document); + rows++; + } + return rows; + } + + /** + * Creates the prepared query for execution + * + * @param database + * the database + * @param object + * the object that is being updated + * + * @return the prepared query. If null is returned a + * insert query will be performed. + */ + protected abstract ONativeSynchQuery> createQuery( + ODatabaseDocumentTx database, T object); + + /** + * Set the parameters in document for object. The + * object can be removed calling {@link ODocument#delete()} and + * returning null + * + * @param document + * the document + * @param object + * the object + * @return the updated document. Can be null + * @throws SQLException + * if any SQL error occur + */ + protected abstract ODocument update(ODocument document, T object) + throws SQLException; + + /** + * Set the parameters for in statement for object + * + * @param document + * the document + * @param object + * the object + * @return the filled document + * @throws SQLException + * if any SQL error occur + */ + protected abstract ODocument insert(ODocument document, T object) + throws SQLException; + + /** + * Return the key mapper. Can be null if no generated keys are used or + * are not important. + * + * @return the key mapper + */ + protected Mapper> keyMapper() { + return null; + } + } + + /** + * An select query that returns a list of objects of type T + * + * @author Rogiel + * + * @param + * the query return type + */ + public static abstract class SelectListQuery implements Query> { + /** + * The logger + */ + private final Logger log = LoggerFactory + .getLogger(SelectListQuery.class); + + @Override + public List query(ODatabaseDocumentTx database) throws SQLException { + Preconditions.checkNotNull(database, "database"); + + log.debug("Starting SELECT List query execution"); + + List result = database.query(createQuery(database)); + final List list = CollectionFactory.newList(); + final Mapper mapper = mapper(); + log.debug("Database returned {}", result); + for (final ODocument document : result) { + log.debug("Mapping row with {}", mapper); + final T obj = mapper.map(document); + if (obj == null) { + log.debug("Mapper {} returned a null row", mapper); + continue; + } + if (obj instanceof Model) + ((Model) obj).setObjectDesire(ObjectDesire.NONE); + log.debug("Mapper {} returned {}", mapper, obj); + list.add(obj); + } + return list; + } + + /** + * Creates the prepared query for execution + * + * @param database + * the database + * + * @return the prepared query + */ + protected abstract ONativeSynchQuery> createQuery( + ODatabaseDocumentTx database); + + /** + * Return the mapper that will bind {@link ResultSet} objects into an + * T object instance. The mapper will need to create the object + * instance. + *

+ * Note: This method will be called for each row, an thus is a + * good idea to create a new instance on each call! + * + * @return the mapper instance + */ + protected abstract Mapper mapper(); + } + + /** + * An select query that returns a single object of type T + * + * @author Rogiel + * + * @param + * the query return type + */ + public static abstract class SelectSingleQuery implements Query { + /** + * The logger + */ + private final Logger log = LoggerFactory + .getLogger(SelectSingleQuery.class); + + @Override + public T query(ODatabaseDocumentTx database) throws SQLException { + Preconditions.checkNotNull(database, "database"); + + log.debug("Starting SELECT single query execution"); + + List result = database.query(createQuery(database)); + final Mapper mapper = mapper(); + log.debug("Database returned {}", result); + for (final ODocument document : result) { + log.debug("Mapping row with {}", mapper); + final T obj = mapper.map(document); + if (obj == null) { + log.debug("Mapper {} returned a null row", mapper); + continue; + } + if (obj instanceof Model) + ((Model) obj).setObjectDesire(ObjectDesire.NONE); + log.debug("Mapper {} returned {}", mapper, obj); + return obj; + } + return null; + } + + /** + * Creates the prepared query for execution + * + * @param database + * the database + * + * @return the prepared query + */ + protected abstract ONativeSynchQuery> createQuery( + ODatabaseDocumentTx database); + + /** + * Return the mapper that will bind {@link ResultSet} objects into an + * T object instance. The mapper will need to create the object + * instance. + *

+ * Note: This method will be called for each row, an thus is a + * good idea to create a new instance on each call! + * + * @return the mapper instance + */ + protected abstract Mapper mapper(); + } + + /** + * The {@link Mapper} maps an {@link ResultSet} into an object T + * + * @author Rogiel + * + * @param + * the object type + */ + public interface Mapper { + /** + * Map the result set value into an object. + *

+ * Note: it is required to call {@link ResultSet#next()}, since + * it is called by the {@link Query}. + * + * @param document + * the resulted document + * @return the created instance + * @throws SQLException + * if any SQL error occur + */ + T map(ODocument document) throws SQLException; + } + + /** + * The cached mapper will try to lookup the result in the cache, before + * create a new instance. If the instance is not found in the cache, then + * the {@link Mapper} implementation is called to create the object. Note + * that the ID, used for the cache lookup, will be reused. After creation, + * the cache is updated. + * + * @author Rogiel + * + * @param + * the object type + * @param + * the id type + */ + public abstract static class CachedMapper, I extends ID> + implements Mapper { + /** + * The logger + */ + private final Logger log = LoggerFactory + .getLogger(SelectSingleQuery.class); + + /** + * The database service instance + */ + private final OrientDatabaseService database; + + /** + * The {@link ID} mapper + */ + private final Mapper idMapper; + + /** + * Creates a new instance + * + * @param database + * the database service + * @param idMapper + * the {@link ID} {@link Mapper} + */ + public CachedMapper(OrientDatabaseService database, Mapper idMapper) { + this.database = database; + this.idMapper = idMapper; + } + + @Override + @SuppressWarnings("unchecked") + public final T map(ODocument document) throws SQLException { + log.debug("Mapping row {} ID with {}", document, idMapper); + final I id = idMapper.map(document); + Preconditions.checkNotNull(id, "id"); + + log.debug("ID={}, locating cached object", id); + + if (database.hasCachedObject(id)) + return (T) database.getCachedObject(id); + + log.debug("Cached object not found, creating..."); + + final T object = map(id, document); + if (object != null) + database.updateCache(id, object); + log.debug("Object {} created", object); + return object; + } + + /** + * Maps an uncached object. Once mapping is complete, it will be added + * to the cache. + * + * @param id + * the object id + * @param document + * the document result + * @return the created object + * @throws SQLException + * if any SQL error occur + */ + protected abstract T map(I id, ODocument document) throws SQLException; + } +} diff --git a/src/main/java/com/l2jserver/service/database/orientdb/AbstractOrientDBDAO.java b/src/main/java/com/l2jserver/service/database/orientdb/AbstractOrientDBDAO.java new file mode 100644 index 000000000..9d3f76979 --- /dev/null +++ b/src/main/java/com/l2jserver/service/database/orientdb/AbstractOrientDBDAO.java @@ -0,0 +1,44 @@ +/* + * This file is part of l2jserver . + * + * l2jserver 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. + * + * l2jserver 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 l2jserver. If not, see . + */ +package com.l2jserver.service.database.orientdb; + +import com.l2jserver.model.Model; +import com.l2jserver.model.id.ID; +import com.l2jserver.service.database.AbstractDAO; +import com.l2jserver.service.database.DatabaseService; +import com.l2jserver.service.database.OrientDatabaseService; + +/** + * @author Rogiel + * @param + * the model type + * @param + * the id type + */ +public abstract class AbstractOrientDBDAO, I extends ID> + extends AbstractDAO { + protected final OrientDatabaseService database; + + /** + * @param database + * the database instance + */ + protected AbstractOrientDBDAO(DatabaseService database) { + super(database); + this.database = (OrientDatabaseService) database; + } +} diff --git a/src/main/java/com/l2jserver/service/database/orientdb/OrientDBCharacterDAO.java b/src/main/java/com/l2jserver/service/database/orientdb/OrientDBCharacterDAO.java new file mode 100644 index 000000000..e4ba5eedc --- /dev/null +++ b/src/main/java/com/l2jserver/service/database/orientdb/OrientDBCharacterDAO.java @@ -0,0 +1,465 @@ +/* + * This file is part of l2jserver . + * + * l2jserver 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. + * + * l2jserver 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 l2jserver. If not, see . + */ +package com.l2jserver.service.database.orientdb; + +import java.sql.SQLException; +import java.util.Collection; +import java.util.List; + +import com.google.inject.Inject; +import com.l2jserver.model.dao.CharacterDAO; +import com.l2jserver.model.id.AccountID; +import com.l2jserver.model.id.object.CharacterID; +import com.l2jserver.model.id.object.ClanID; +import com.l2jserver.model.id.object.provider.CharacterIDProvider; +import com.l2jserver.model.id.object.provider.ClanIDProvider; +import com.l2jserver.model.id.provider.AccountIDProvider; +import com.l2jserver.model.id.template.CharacterTemplateID; +import com.l2jserver.model.id.template.provider.CharacterTemplateIDProvider; +import com.l2jserver.model.template.CharacterTemplate; +import com.l2jserver.model.template.actor.ActorSex; +import com.l2jserver.model.template.character.CharacterClass; +import com.l2jserver.model.template.character.CharacterRace; +import com.l2jserver.model.world.Clan; +import com.l2jserver.model.world.L2Character; +import com.l2jserver.model.world.character.CharacterAppearance; +import com.l2jserver.model.world.character.CharacterAppearance.CharacterFace; +import com.l2jserver.model.world.character.CharacterAppearance.CharacterHairColor; +import com.l2jserver.model.world.character.CharacterAppearance.CharacterHairStyle; +import com.l2jserver.service.database.DatabaseService; +import com.l2jserver.service.database.OrientDatabaseService.CachedMapper; +import com.l2jserver.service.database.OrientDatabaseService.InsertUpdateQuery; +import com.l2jserver.service.database.OrientDatabaseService.Mapper; +import com.l2jserver.service.database.OrientDatabaseService.SelectListQuery; +import com.l2jserver.service.database.OrientDatabaseService.SelectSingleQuery; +import com.l2jserver.util.geometry.Point3D; +import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; +import com.orientechnologies.orient.core.query.nativ.ONativeSynchQuery; +import com.orientechnologies.orient.core.query.nativ.OQueryContextNativeSchema; +import com.orientechnologies.orient.core.record.impl.ODocument; + +/** + * @author Rogiel + * + */ +public class OrientDBCharacterDAO extends + AbstractOrientDBDAO implements CharacterDAO { + /** + * The {@link CharacterID} factory + */ + private final CharacterIDProvider idFactory; + /** + * The {@link CharacterTemplateID} factory + */ + private final CharacterTemplateIDProvider templateIdFactory; + /** + * The {@link AccountID} factory + */ + private final AccountIDProvider accountIdFactory; + /** + * The {@link ClanID} factory + */ + private final ClanIDProvider clanIdFactory; + + /** + * Character table name + */ + public static final String CLASS_NAME = L2Character.class.getSimpleName(); + // FIELDS + public static final String CHAR_ID = "character_id"; + public static final String ACCOUNT_ID = "account_id"; + public static final String CLAN_ID = "clan_id"; + public static final String NAME = "name"; + + public static final String RACE = "race"; + public static final String CLASS = "class"; + public static final String SEX = "sex"; + + public static final String LEVEL = "level"; + public static final String EXPERIENCE = "experience"; + public static final String SP = "sp"; + + public static final String HP = "hp"; + public static final String MP = "mp"; + public static final String CP = "cp"; + + public static final String POINT_X = "point_x"; + public static final String POINT_Y = "point_y"; + public static final String POINT_Z = "point_z"; + public static final String POINT_ANGLE = "point_angle"; + + public static final String APPEARANCE_HAIR_STYLE = "appearance_hair_style"; + public static final String APPEARANCE_HAIR_COLOR = "appearance_hair_color"; + public static final String APPEARANCE_FACE = "apperance_face"; + + /** + * The mapper for {@link CharacterID} + */ + private final Mapper idMapper = new Mapper() { + @Override + public CharacterID map(ODocument document) throws SQLException { + return idFactory.resolveID((Integer) document.field(CHAR_ID)); + } + }; + + /** + * The {@link Mapper} for {@link L2Character} + */ + private final Mapper mapper = new CachedMapper( + database, idMapper) { + @Override + protected L2Character map(CharacterID id, ODocument document) + throws SQLException { + final CharacterClass charClass = (CharacterClass) document + .field(CLASS); + final CharacterTemplateID templateId = templateIdFactory + .resolveID(charClass.id); + final CharacterTemplate template = templateId.getTemplate(); + + final L2Character character = template.create(); + + character.setID(id); + character.setAccountID(accountIdFactory.resolveID((String) document + .field(ACCOUNT_ID))); + if (document.containsField(CLAN_ID)) + character.setClanID(clanIdFactory.resolveID((Integer) document + .field(CLAN_ID))); + + character.setName((String) document.field(NAME)); + + character.setRace((CharacterRace) document.field(RACE)); + character.setCharacterClass((CharacterClass) document.field(CLASS)); + character.setSex((ActorSex) document.field(SEX)); + + character.setLevel((Integer) document.field(LEVEL)); + character.setExperience((Long) document.field(EXPERIENCE)); + character.setSP((Integer) document.field(SP)); + + character.setHP((Double) document.field(HP)); + character.setMP((Double) document.field(MP)); + character.setCP((Double) document.field(CP)); + + character.setPoint(Point3D.fromXYZA( + (Integer) document.field(POINT_X), + (Integer) document.field(POINT_Y), + (Integer) document.field(POINT_Z), + (Double) document.field(POINT_ANGLE))); + + // appearance + character.getAppearance().setHairStyle( + (CharacterHairStyle) document.field(APPEARANCE_HAIR_STYLE)); + character.getAppearance().setHairColor( + (CharacterHairColor) document.field(APPEARANCE_HAIR_COLOR)); + character.getAppearance().setFace( + (CharacterFace) document.field(APPEARANCE_FACE)); + + return character; + } + }; + + /** + * @param database + * the database service + * @param idFactory + * the character id provider + * @param templateIdFactory + * the template id provider + * @param accountIdFactory + * the account id provider + * @param clanIdFactory + * the clan id provider + */ + @Inject + protected OrientDBCharacterDAO(DatabaseService database, + final CharacterIDProvider idFactory, + CharacterTemplateIDProvider templateIdFactory, + AccountIDProvider accountIdFactory, ClanIDProvider clanIdFactory) { + super(database); + this.idFactory = idFactory; + this.templateIdFactory = templateIdFactory; + this.accountIdFactory = accountIdFactory; + this.clanIdFactory = clanIdFactory; + } + + @Override + public L2Character select(final CharacterID id) { + return database.query(new SelectSingleQuery() { + @Override + protected ONativeSynchQuery> createQuery( + ODatabaseDocumentTx database) { + return new ONativeSynchQuery>( + database, CLASS_NAME, + new OQueryContextNativeSchema()) { + private static final long serialVersionUID = 1L; + + @Override + public boolean filter( + OQueryContextNativeSchema criteria) { + return criteria.field(CHAR_ID).eq(id.getID()).go(); + }; + }; + } + + @Override + protected Mapper mapper() { + return mapper; + } + }); + } + + @Override + public Collection selectIDs() { + return database.query(new SelectListQuery() { + @Override + protected ONativeSynchQuery> createQuery( + ODatabaseDocumentTx database) { + return new ONativeSynchQuery>( + database, CLASS_NAME, + new OQueryContextNativeSchema()) { + private static final long serialVersionUID = 1L; + + @Override + public boolean filter( + OQueryContextNativeSchema criteria) { + return true; + }; + }; + } + + @Override + protected Mapper mapper() { + return idMapper; + } + }); + } + + @Override + public boolean insert(L2Character object) { + return database.query(new InsertUpdateQuery(object) { + @Override + protected ONativeSynchQuery> createQuery( + ODatabaseDocumentTx database, L2Character object) { + return null; + } + + @Override + protected ODocument update(ODocument document, L2Character character) + throws SQLException { + return null; + } + + @Override + protected ODocument insert(ODocument document, L2Character character) + throws SQLException { + final CharacterAppearance appearance = character + .getAppearance(); + + document.field(CHAR_ID, character.getID().getID()); + document.field(ACCOUNT_ID, character.getAccountID().getID()); + if (character.getClanID() != null) + document.field(CLAN_ID, character.getClanID().getID()); + + document.field(NAME, character.getName()); + + document.field(RACE, character.getRace().name()); + document.field(CLASS, character.getCharacterClass().name()); + document.field(SEX, character.getSex().name()); + + document.field(LEVEL, character.getLevel()); + document.field(EXPERIENCE, character.getExperience()); + document.field(SP, character.getSP()); + + document.field(HP, character.getHP()); + document.field(MP, character.getMP()); + document.field(CP, character.getCP()); + + document.field(POINT_X, character.getPoint().getX()); + document.field(POINT_Y, character.getPoint().getY()); + document.field(POINT_Z, character.getPoint().getZ()); + document.field(POINT_ANGLE, character.getPoint().getAngle()); + + // appearance + document.field(APPEARANCE_HAIR_STYLE, appearance.getHairStyle() + .name()); + document.field(APPEARANCE_HAIR_COLOR, appearance.getHairColor() + .name()); + document.field(APPEARANCE_FACE, appearance.getFace().name()); + + return document; + } + }) != 0; + } + + @Override + public boolean update(final L2Character character) { + return database.query(new InsertUpdateQuery(character) { + @Override + protected ONativeSynchQuery> createQuery( + ODatabaseDocumentTx database, final L2Character character) { + return new ONativeSynchQuery>( + database, CLASS_NAME, + new OQueryContextNativeSchema()) { + private static final long serialVersionUID = 1L; + + @Override + public boolean filter( + OQueryContextNativeSchema criteria) { + return criteria.field(CHAR_ID) + .eq(character.getID().getID()).go(); + }; + }; + } + + @Override + protected ODocument update(ODocument document, L2Character character) + throws SQLException { + final CharacterAppearance appearance = character + .getAppearance(); + + document.field(ACCOUNT_ID, character.getAccountID().getID()); + if (character.getClanID() != null) + document.field(CLAN_ID, character.getClanID().getID()); + + document.field(NAME, character.getName()); + + document.field(RACE, character.getRace().name()); + document.field(CLASS, character.getCharacterClass().name()); + document.field(SEX, character.getSex().name()); + + document.field(LEVEL, character.getLevel()); + document.field(EXPERIENCE, character.getExperience()); + document.field(SP, character.getSP()); + + document.field(HP, character.getHP()); + document.field(MP, character.getMP()); + document.field(CP, character.getCP()); + + document.field(POINT_X, character.getPoint().getX()); + document.field(POINT_Y, character.getPoint().getY()); + document.field(POINT_Z, character.getPoint().getZ()); + document.field(POINT_ANGLE, character.getPoint().getAngle()); + + // appearance + document.field(APPEARANCE_HAIR_STYLE, appearance.getHairStyle() + .name()); + document.field(APPEARANCE_HAIR_COLOR, appearance.getHairColor() + .name()); + document.field(APPEARANCE_FACE, appearance.getFace().name()); + + return document; + } + + @Override + protected ODocument insert(ODocument document, L2Character character) + throws SQLException { + return null; + } + }) != 0; + } + + @Override + public boolean delete(L2Character object) { + return database.query(new InsertUpdateQuery(object) { + @Override + protected ONativeSynchQuery> createQuery( + ODatabaseDocumentTx database, final L2Character character) { + return new ONativeSynchQuery>( + database, CLASS_NAME, + new OQueryContextNativeSchema()) { + private static final long serialVersionUID = 1L; + + @Override + public boolean filter( + OQueryContextNativeSchema criteria) { + return criteria.field(CHAR_ID) + .eq(character.getID().getID()).go(); + }; + }; + } + + @Override + protected ODocument update(ODocument document, L2Character character) + throws SQLException { + document.delete(); + return null; + } + + @Override + protected ODocument insert(ODocument document, L2Character character) + throws SQLException { + return null; + } + }) != 0; + } + + @Override + public void load(Clan clan) { + + } + + @Override + public L2Character selectByName(final String name) { + return database.query(new SelectSingleQuery() { + @Override + protected ONativeSynchQuery> createQuery( + ODatabaseDocumentTx database) { + return new ONativeSynchQuery>( + database, CLASS_NAME, + new OQueryContextNativeSchema()) { + private static final long serialVersionUID = 1L; + + @Override + public boolean filter( + OQueryContextNativeSchema criteria) { + return criteria.field(NAME).eq(name).go(); + }; + }; + } + + @Override + protected Mapper mapper() { + return mapper; + } + }); + } + + @Override + public List selectByAccount(final AccountID account) { + return database.query(new SelectListQuery() { + @Override + protected ONativeSynchQuery> createQuery( + ODatabaseDocumentTx database) { + return new ONativeSynchQuery>( + database, CLASS_NAME, + new OQueryContextNativeSchema()) { + private static final long serialVersionUID = 1L; + + @Override + public boolean filter( + OQueryContextNativeSchema criteria) { + return criteria.field(ACCOUNT_ID).eq(account.getID()).go(); + }; + }; + } + + @Override + protected Mapper mapper() { + return mapper; + } + }); + } +}