Java tutorial
/* * Copyright (c) 2010, Stanislav Muhametsin. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.qi4j.entitystore.sql; import java.io.IOException; import java.io.PrintWriter; import java.io.Reader; import java.io.StringWriter; import java.io.Writer; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONTokener; import org.json.JSONWriter; import org.qi4j.api.cache.CacheOptions; import org.qi4j.api.common.Optional; import org.qi4j.api.common.QualifiedName; import org.qi4j.api.entity.EntityReference; import org.qi4j.api.injection.scope.Service; import org.qi4j.api.injection.scope.Structure; import org.qi4j.api.injection.scope.This; import org.qi4j.api.io.Input; import org.qi4j.api.io.Output; import org.qi4j.api.io.Receiver; import org.qi4j.api.io.Sender; import org.qi4j.api.service.Activatable; import org.qi4j.api.structure.Application; import org.qi4j.api.unitofwork.EntityTypeNotFoundException; import org.qi4j.api.usecase.Usecase; import org.qi4j.api.usecase.UsecaseBuilder; import org.qi4j.entitystore.map.MapEntityStore; import org.qi4j.entitystore.map.MapEntityStoreMixin; import org.qi4j.entitystore.map.Migration; import org.qi4j.entitystore.map.StateStore; import org.qi4j.entitystore.sql.internal.DatabaseSQLService; import org.qi4j.entitystore.sql.internal.DatabaseSQLService.EntityValueResult; import org.qi4j.library.sql.api.SQLEntityState; import org.qi4j.library.sql.api.SQLEntityState.DefaultSQLEntityState; import org.qi4j.library.sql.common.SQLUtil; import org.qi4j.spi.entity.EntityDescriptor; import org.qi4j.spi.entity.EntityState; import org.qi4j.spi.entity.EntityStatus; import org.qi4j.spi.entity.EntityType; import org.qi4j.spi.entity.association.AssociationDescriptor; import org.qi4j.spi.entitystore.DefaultEntityStoreUnitOfWork; import org.qi4j.spi.entitystore.EntityNotFoundException; import org.qi4j.spi.entitystore.EntityStore; import org.qi4j.spi.entitystore.EntityStoreException; import org.qi4j.spi.entitystore.EntityStoreSPI; import org.qi4j.spi.entitystore.EntityStoreUnitOfWork; import org.qi4j.spi.entitystore.StateCommitter; import org.qi4j.spi.entitystore.helpers.DefaultEntityState; import org.qi4j.spi.property.PropertyDescriptor; import org.qi4j.spi.property.PropertyType; import org.qi4j.spi.property.PropertyTypeDescriptor; import org.qi4j.spi.structure.ModuleSPI; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Most of this code is copy-paste from {@link MapEntityStoreMixin}. TODO refactor stuff that has to do with general * things than actual MapEntityStore from {@link MapEntityStoreMixin} so that this class could extend some * "AbstractJSONEntityStoreMixin". * */ public class SQLEntityStoreMixin implements EntityStore, EntityStoreSPI, StateStore, Activatable { private static final Logger LOGGER = LoggerFactory.getLogger(SQLEntityStoreMixin.class); @Service private DatabaseSQLService database; @This private EntityStoreSPI entityStoreSPI; @Structure private Application application; @Optional @Service private Migration migration; private String uuid; private Integer count; public void activate() throws Exception { uuid = UUID.randomUUID().toString() + "-"; count = 0; database.startDatabase(); } public void passivate() throws Exception { database.stopDatabase(); } public StateCommitter applyChanges(final EntityStoreUnitOfWork unitofwork, final Iterable<EntityState> states) { return new StateCommitter() { public void commit() { Connection connection = null; PreparedStatement insertPS = null; PreparedStatement updatePS = null; PreparedStatement removePS = null; try { connection = database.getConnection(); insertPS = database.prepareInsertEntityStatement(connection); updatePS = database.prepareUpdateEntityStatement(connection); removePS = database.prepareRemoveEntityStatement(connection); for (EntityState state : states) { EntityStatus status = state.status(); DefaultEntityState defState = ((SQLEntityState) state).getDefaultEntityState(); Long entityPK = ((SQLEntityState) state).getEntityPK(); if (EntityStatus.REMOVED.equals(status)) { database.populateRemoveEntityStatement(removePS, entityPK, state.identity()); removePS.addBatch(); } else { StringWriter writer = new StringWriter(); writeEntityState(defState, writer, unitofwork.identity()); writer.flush(); if (EntityStatus.UPDATED.equals(status)) { Long entityOptimisticLock = ((SQLEntityState) state).getEntityOptimisticLock(); database.populateUpdateEntityStatement(updatePS, entityPK, entityOptimisticLock, defState.identity(), writer.toString(), unitofwork.currentTime()); updatePS.addBatch(); } else if (EntityStatus.NEW.equals(status)) { database.populateInsertEntityStatement(insertPS, entityPK, defState.identity(), writer.toString(), unitofwork.currentTime()); insertPS.addBatch(); } } } removePS.executeBatch(); insertPS.executeBatch(); updatePS.executeBatch(); connection.commit(); } catch (SQLException sqle) { SQLUtil.rollbackQuietly(connection); if (LOGGER.isDebugEnabled()) { StringWriter sb = new StringWriter(); sb.append( "SQLException during commit, logging nested exceptions before throwing EntityStoreException:\n"); SQLException e = sqle; while (e != null) { e.printStackTrace(new PrintWriter(sb, true)); e = e.getNextException(); } LOGGER.debug(sb.toString()); } throw new EntityStoreException(sqle); } catch (RuntimeException re) { SQLUtil.rollbackQuietly(connection); throw new EntityStoreException(re); } finally { SQLUtil.closeQuietly(insertPS); SQLUtil.closeQuietly(updatePS); SQLUtil.closeQuietly(removePS); SQLUtil.closeQuietly(connection); } } public void cancel() { } }; } public EntityState getEntityState(EntityStoreUnitOfWork unitOfWork, EntityReference entityRef) { EntityValueResult valueResult = getValue(entityRef); return new DefaultSQLEntityState( readEntityState((DefaultEntityStoreUnitOfWork) unitOfWork, valueResult.getReader()), valueResult.getEntityPK(), valueResult.getEntityOptimisticLock()); } public EntityState newEntityState(EntityStoreUnitOfWork unitOfWork, EntityReference entityRef, EntityDescriptor entityDescriptor) { return new DefaultSQLEntityState( new DefaultEntityState((DefaultEntityStoreUnitOfWork) unitOfWork, entityRef, entityDescriptor), database.newPKForEntity(), null); } public EntityStoreUnitOfWork newUnitOfWork(Usecase usecase, ModuleSPI module, long currentTime) { return new DefaultEntityStoreUnitOfWork(entityStoreSPI, newUnitOfWorkId(), module, usecase, currentTime); } public Input<EntityState, EntityStoreException> entityStates(final ModuleSPI module) { return new Input<EntityState, EntityStoreException>() { @Override public <ReceiverThrowableType extends Throwable> void transferTo( Output<? super EntityState, ReceiverThrowableType> output) throws EntityStoreException, ReceiverThrowableType { output.receiveFrom(new Sender<EntityState, EntityStoreException>() { @Override public <ReceiverThrowableType extends Throwable> void sendTo( Receiver<? super EntityState, ReceiverThrowableType> receiver) throws ReceiverThrowableType, EntityStoreException { Connection connection = null; PreparedStatement ps = null; ResultSet rs = null; UsecaseBuilder builder = UsecaseBuilder.buildUsecase("qi4j.entitystore.sql.visit"); Usecase usecase = builder.with(CacheOptions.NEVER).newUsecase(); final DefaultEntityStoreUnitOfWork uow = new DefaultEntityStoreUnitOfWork(entityStoreSPI, newUnitOfWorkId(), module, usecase, System.currentTimeMillis()); try { connection = database.getConnection(); ps = database.prepareGetAllEntitiesStatement(connection); database.populateGetAllEntitiesStatement(ps); rs = ps.executeQuery(); while (rs.next()) { receiver.receive(readEntityState(uow, database.getEntityValue(rs).getReader())); } } catch (SQLException sqle) { throw new EntityStoreException(sqle); } finally { SQLUtil.closeQuietly(rs); SQLUtil.closeQuietly(ps); SQLUtil.closeQuietly(connection); } } }); } }; } @SuppressWarnings("ValueOfIncrementOrDecrementUsed") protected String newUnitOfWorkId() { return uuid + Integer.toHexString(count++); } protected DefaultEntityState readEntityState(DefaultEntityStoreUnitOfWork unitOfWork, Reader entityState) throws EntityStoreException { try { ModuleSPI module = unitOfWork.module(); JSONObject jsonObject = new JSONObject(new JSONTokener(entityState)); EntityStatus status = EntityStatus.LOADED; String version = jsonObject.getString("version"); long modified = jsonObject.getLong("modified"); String identity = jsonObject.getString("identity"); // Check if version is correct String currentAppVersion = jsonObject.optString(MapEntityStore.JSONKeys.application_version.name(), "0.0"); if (!currentAppVersion.equals(application.version())) { if (migration != null) { migration.migrate(jsonObject, application.version(), this); } else { // Do nothing - set version to be correct jsonObject.put(MapEntityStore.JSONKeys.application_version.name(), application.version()); } LOGGER.trace("Updated version nr on {} from {} to {}", new Object[] { identity, currentAppVersion, application.version() }); // State changed status = EntityStatus.UPDATED; } String type = jsonObject.getString("type"); EntityDescriptor entityDescriptor = module.entityDescriptor(type); if (entityDescriptor == null) { throw new EntityTypeNotFoundException(type); } Map<QualifiedName, Object> properties = new HashMap<QualifiedName, Object>(); JSONObject props = jsonObject.getJSONObject("properties"); for (PropertyDescriptor propertyDescriptor : entityDescriptor.state().properties()) { Object jsonValue; try { jsonValue = props.get(propertyDescriptor.qualifiedName().name()); } catch (JSONException e) { // Value not found, default it Object initialValue = propertyDescriptor.initialValue(); properties.put(propertyDescriptor.qualifiedName(), initialValue); status = EntityStatus.UPDATED; continue; } if (jsonValue == JSONObject.NULL) { properties.put(propertyDescriptor.qualifiedName(), null); } else { Object value = ((PropertyTypeDescriptor) propertyDescriptor).propertyType().type() .fromJSON(jsonValue, module); properties.put(propertyDescriptor.qualifiedName(), value); } } Map<QualifiedName, EntityReference> associations = new HashMap<QualifiedName, EntityReference>(); JSONObject assocs = jsonObject.getJSONObject("associations"); for (AssociationDescriptor associationType : entityDescriptor.state().associations()) { try { Object jsonValue = assocs.get(associationType.qualifiedName().name()); EntityReference value = jsonValue == JSONObject.NULL ? null : EntityReference.parseEntityReference((String) jsonValue); associations.put(associationType.qualifiedName(), value); } catch (JSONException e) { // Association not found, default it to null associations.put(associationType.qualifiedName(), null); status = EntityStatus.UPDATED; } } Map<QualifiedName, List<EntityReference>> manyAssociations = createManyAssociations(jsonObject, entityDescriptor); Map<QualifiedName, Map<String, EntityReference>> namedAssociations = createNamedAssociations(jsonObject, entityDescriptor); return new DefaultEntityState(unitOfWork, version, modified, EntityReference.parseEntityReference(identity), status, entityDescriptor, properties, associations, manyAssociations, namedAssociations); } catch (JSONException e) { throw new EntityStoreException(e); } } private Map<QualifiedName, List<EntityReference>> createManyAssociations(JSONObject jsonObject, EntityDescriptor entityDescriptor) throws JSONException { JSONObject manyAssocs = jsonObject.getJSONObject("manyassociations"); Map<QualifiedName, List<EntityReference>> manyAssociations = new HashMap<QualifiedName, List<EntityReference>>(); for (AssociationDescriptor manyAssociationType : entityDescriptor.state().manyAssociations()) { List<EntityReference> references = new ArrayList<EntityReference>(); try { JSONArray jsonValues = manyAssocs.getJSONArray(manyAssociationType.qualifiedName().name()); for (int i = 0; i < jsonValues.length(); i++) { Object jsonValue = jsonValues.getString(i); EntityReference value = jsonValue == JSONObject.NULL ? null : EntityReference.parseEntityReference((String) jsonValue); references.add(value); } manyAssociations.put(manyAssociationType.qualifiedName(), references); } catch (JSONException e) { // ManyAssociation not found, default to empty one manyAssociations.put(manyAssociationType.qualifiedName(), references); } } return manyAssociations; } private Map<QualifiedName, Map<String, EntityReference>> createNamedAssociations(JSONObject jsonObject, EntityDescriptor entityDescriptor) throws JSONException { JSONObject namedAssocs = jsonObject.getJSONObject("namedassociations"); Map<QualifiedName, Map<String, EntityReference>> namedAssociations = new HashMap<QualifiedName, Map<String, EntityReference>>(); for (AssociationDescriptor namedAssociationType : entityDescriptor.state().namedAssociations()) { Map<String, EntityReference> references = new HashMap<String, EntityReference>(); try { JSONObject jsonValues = namedAssocs.getJSONObject(namedAssociationType.qualifiedName().name()); for (String name : jsonValues) { Object jsonValue = jsonValues.getString(name); EntityReference value; if (jsonValue == JSONObject.NULL) { value = null; } else { value = EntityReference.parseEntityReference((String) jsonValue); } references.put(name, value); } namedAssociations.put(namedAssociationType.qualifiedName(), references); } catch (JSONException e) { // NamedAssociation not found, default to empty one namedAssociations.put(namedAssociationType.qualifiedName(), references); } } return namedAssociations; } public JSONObject getState(String id) throws IOException { Reader reader = getValue(EntityReference.parseEntityReference(id)).getReader(); JSONObject jsonObject; try { jsonObject = new JSONObject(new JSONTokener(reader)); } catch (JSONException e) { throw new IOException(e); } reader.close(); return jsonObject; } protected EntityValueResult getValue(EntityReference ref) { Connection connection = null; PreparedStatement ps = null; ResultSet rs = null; try { connection = database.getConnection(); ps = database.prepareGetEntityStatement(connection); database.populateGetEntityStatement(ps, ref); rs = ps.executeQuery(); if (!rs.next()) { throw new EntityNotFoundException(ref); } EntityValueResult result = database.getEntityValue(rs); return result; } catch (SQLException sqle) { throw new EntityStoreException("Unable to get Entity " + ref, sqle); } finally { SQLUtil.closeQuietly(rs); SQLUtil.closeQuietly(ps); SQLUtil.closeQuietly(connection); } } protected void writeEntityState(DefaultEntityState state, Writer writer, String version) throws EntityStoreException { try { JSONWriter json = new JSONWriter(writer); JSONWriter properties = json.object().key("identity").value(state.identity().identity()) .key("application_version").value(application.version()).key("type") .value(state.entityDescriptor().entityType().type().name()).key("version").value(version) .key("modified").value(state.lastModified()).key("properties").object(); EntityType entityType = state.entityDescriptor().entityType(); for (PropertyType propertyType : entityType.properties()) { Object value = state.properties().get(propertyType.qualifiedName()); json.key(propertyType.qualifiedName().name()); if (value == null) { json.value(null); } else { propertyType.type().toJSON(value, json); } } JSONWriter associations = properties.endObject().key("associations").object(); for (Map.Entry<QualifiedName, EntityReference> stateNameEntityReferenceEntry : state.associations() .entrySet()) { EntityReference value = stateNameEntityReferenceEntry.getValue(); associations.key(stateNameEntityReferenceEntry.getKey().name()) .value(value != null ? value.identity() : null); } JSONWriter manyAssociations = associations.endObject().key("manyassociations").object(); for (Map.Entry<QualifiedName, List<EntityReference>> stateNameListEntry : state.manyAssociations() .entrySet()) { JSONWriter assocs = manyAssociations.key(stateNameListEntry.getKey().name()).array(); for (EntityReference entityReference : stateNameListEntry.getValue()) { assocs.value(entityReference.identity()); } assocs.endArray(); } JSONWriter namedAssociations = associations.endObject().key("namedassociations").object(); for (Map.Entry<QualifiedName, Map<String, EntityReference>> stateNameListEntry : state .namedAssociations().entrySet()) { JSONWriter assocs = namedAssociations.key(stateNameListEntry.getKey().name()).object(); Map<String, EntityReference> value = stateNameListEntry.getValue(); for (Map.Entry<String, EntityReference> entry : value.entrySet()) { assocs.key(entry.getKey()).value(entry.getValue()); } assocs.endObject(); } manyAssociations.endObject().endObject(); } catch (JSONException e) { throw new EntityStoreException("Could not store EntityState", e); } } }