/*
* Copyright 2012 Adaptrex, LLC
*
* 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 com.adaptrex.core.persistence.cayenne;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.cayenne.ObjectContext;
import org.apache.cayenne.ObjectId;
import org.apache.cayenne.access.DataContext;
import org.apache.cayenne.access.DataDomain;
import org.apache.cayenne.configuration.server.ServerRuntime;
import org.apache.cayenne.exp.Expression;
import org.apache.cayenne.exp.ExpressionFactory;
import org.apache.cayenne.lifecycle.id.IdCoder;
import org.apache.cayenne.map.EntityResolver;
import org.apache.cayenne.map.ObjEntity;
import org.apache.cayenne.query.ObjectIdQuery;
import org.apache.cayenne.query.SelectQuery;
import org.apache.log4j.Logger;
import com.adaptrex.core.ext.ExtConfig;
import com.adaptrex.core.ext.ExtTypeFormatter;
import com.adaptrex.core.ext.ModelInstance;
import com.adaptrex.core.persistence.PersistenceTools;
import com.adaptrex.core.persistence.api.AdaptrexEntityType;
import com.adaptrex.core.persistence.api.AdaptrexPersistenceManager;
import com.adaptrex.core.persistence.api.AdaptrexStoreData;
import com.adaptrex.core.security.SecurityModel;
/**
* CayennePersistenceManager is an Adaptrex wrapper around a single Cayenne DataDomain
* providing access to Adaptrex specific functionality. When Adaptrex requires a
* specific CayennePersistenceManager it retrieves it from the AdaptrexRegistry. If
* one doesn't exist, a new one is created and stored in the registry for later
* retrieval.
*/
public class CayennePersistenceManager implements AdaptrexPersistenceManager {
private static Logger log = Logger.getLogger(CayennePersistenceManager.class);
/**
* The factory is a Cayenne ServerRuntime used to create ObjectContext instances
*/
private ServerRuntime factory;
/**
* Read only ObjectContexts can be shared across threads and users. We
* only need to create it once for the lifetime of the application.
*/
private DataContext readOnlySession;
private String name;
private DataDomain dataDomain;
/**
* Creates a persistence manager for a specific DataDomain referenced by the
* factoryName parameter. The factoryName should not include the .xml extension
* <br /><br />
* To create a persistence manager for the domain configured in
* cayenne-secondarydomain.xml, you would use a factoryName "cayenne-secondarydomain"
* @throws Exception
*/
public CayennePersistenceManager(String factoryName) {
if (factoryName == null) {
throw new RuntimeException(
"Cayenne requires a default factory " +
"name to be specified in adaptrex.properties");
}
this.factory = new ServerRuntime(factoryName + ".xml");
this.readOnlySession = (DataContext) this.factory.getContext();
this.name = factoryName;
this.dataDomain = factory.getDataDomain();
try {
initialize();
} catch (Exception e) {
throw new RuntimeException(e);
}
log.info("Creating CayennePersistenceManager for DataDomain " + factoryName);
}
public CayennePersistenceManager(ServerRuntime factory) {
this.factory = factory;
this.readOnlySession = (DataContext) this.factory.getContext();
this.dataDomain = factory.getDataDomain();
this.name = dataDomain.getName();
try {
initialize();
} catch (Exception e) {
throw new RuntimeException(e);
}
log.info("Creating CayennePersistenceManager for DataDomain " + this.name);
}
private Map<String,CayenneEntityType> adaptrexEntities;
private void initialize() throws Exception {
SecurityModel securityModel = PersistenceTools.getSecurityModel();
adaptrexEntities = new HashMap<String,CayenneEntityType>();
for (ObjEntity objEntity : this.factory.getDataDomain().getEntityResolver().getObjEntities()) {
CayenneEntityType adaptrexEntity = new CayenneEntityType(objEntity, securityModel);
adaptrexEntities.put(adaptrexEntity.getName(), adaptrexEntity);
}
}
@Override
public AdaptrexEntityType getAdaptrexEntity(String name) {
return adaptrexEntities.get(name);
}
private CayenneEntityType getCayenneEntity(String name) {
return adaptrexEntities.get(name);
}
@Override
public String getName() {
return this.name;
}
/**
* Returns an ObjectContext for the DataDomain this persistence manager represents.
* getSession should only be used for read/write ObjectContexts as it generates
* a new context on each call. To get a shared read only ObjectContext, call
* getReadOnlySession instead.
*/
@Override
public Object getNativeSession() {
return this.factory.getContext();
}
ObjectContext getObjectContext() {
return this.factory.getContext();
}
/**
* Returns a shared read only ObjectContext for the DataDomain this persistence
* manager represents. To get a new ObjectContext for read/write operations, use
* getSession instead.<br /><br />
* This read only "session" is specific to Cayenne. If it's used directly by
* Adaptrex based applications, they will be Cayenne specific and not portable
* to other ORMs
*/
ObjectContext getReadOnlyObjectContext() {
return this.readOnlySession;
}
/**
* Shuts down the ServerRuntime this persistence manager uses to generate its
* ObjectContexts. This should be called to clean up Cayenne when the container
* is shut down.
*/
@Override
public void shutdown() {
log.info("Shutting down ObjectContect for " + this.factory.getDataDomain().getName());
this.factory.shutdown();
}
/**
* Get a persistent entity based on an id for a specific class. CayennePersistenceManager
* use a string representation of Cayenne's ObjectId to identify individual objects.
*/
@Override
public Object getEntity(Class<?> clazz, Object id) {
return getEntity(null, clazz, id);
}
/**
* Get a persistent entity based on a key value pair for a specific class.
*/
@Override
public Object getEntity(Class<?> clazz, String key, Object value) {
return getEntity(null, clazz, key, value);
}
/**
* Get a persistent entity based on an id for a specific class. CayennePersistenceManager
* use a string representation of Cayenne's ObjectId to identify individual objects.
*/
private Object getEntity(Object objectContext, Class<?> clazz, Object id) {
try {
ObjectContext context = objectContext == null ? this.getReadOnlyObjectContext() : (ObjectContext) objectContext;
EntityResolver entityResolver = this.factory.getDataDomain().getEntityResolver();
IdCoder idCoder = new IdCoder(entityResolver);
ObjectId objectId = idCoder.getObjectId((String) id);
ObjectIdQuery query = new ObjectIdQuery(objectId);
List<?> list = context.performQuery(query);
return list.size() == 0 ? null : list.get(0);
} catch (Exception e) {
return null;
}
}
/**
* Get a persistent entity based on a key value pair for a specific class.
*/
private Object getEntity(Object objectContext, Class<?> clazz, String key, Object value) {
try {
ObjectContext context = objectContext == null ? this.getReadOnlyObjectContext() : (ObjectContext) objectContext;
Expression exp = ExpressionFactory.matchExp(key, value);
SelectQuery query = new SelectQuery(clazz, exp);
List<?> list = context.performQuery(query);
return list.size() == 0 ? null : list.get(0);
} catch (Exception e) {
return null;
}
}
/*
* Get StoreData
*/
@Override
public AdaptrexStoreData getStoreData(ExtConfig extConfig) {
return new CayenneStoreData(extConfig);
}
@Override
public ModelInstance getModelInstance(ExtConfig extConfig, Object id) throws Exception {
Object entity = this.getEntity(extConfig.getEntityClass(), id);
return new ModelInstance(extConfig, entity);
}
@Override
public ModelInstance getModelInstance(ExtConfig extConfig, String key, Object value) throws Exception {
Object entity = this.getEntity(extConfig.getEntityClass(), key, value);
return new ModelInstance(extConfig, entity);
}
@Override
public ModelInstance createModelInstance(ExtConfig extConfig, Map<String, Object> data) {
try {
ObjectContext objectContext = getObjectContext();
Class<?> clazz = extConfig.getEntityClass();
Object entity = objectContext.newObject(clazz);
this.updateEntity(objectContext, clazz, entity, data);
return new ModelInstance(extConfig, entity);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public ModelInstance updateModelInstance(ExtConfig extConfig, Object id, Map<String, Object> data) throws Exception {
return this.updateModelInstance(extConfig, id, null, null, data);
}
@Override
public ModelInstance updateModelInstance(ExtConfig extConfig, String key, Object value, Map<String, Object> data) throws Exception {
return this.updateModelInstance(extConfig, null, key, value, data);
}
private ModelInstance updateModelInstance(ExtConfig extConfig, Object id, String key, Object value, Map<String, Object> data) throws Exception {
ObjectContext objectContext = getObjectContext();
Class<?> clazz = extConfig.getEntityClass();
Object entity = id != null
? this.getEntity(objectContext, clazz, id)
: this.getEntity(objectContext, clazz, key, value);
this.updateEntity(objectContext, clazz, entity, data);
return new ModelInstance(extConfig, entity);
}
@Override
public ModelInstance deleteModelInstance(ExtConfig extConfig, Object id) throws Exception {
return this.deleteModelInstance(extConfig, id, null, null);
}
@Override
public ModelInstance deleteModelInstance(ExtConfig extConfig, String key, Object value) throws Exception {
return this.deleteModelInstance(extConfig, null, key, value);
}
private ModelInstance deleteModelInstance(ExtConfig extConfig, Object id, String key, Object value) throws Exception {
ObjectContext objectContext = getObjectContext();
Class<?> clazz = extConfig.getEntityClass();
Object entity = id != null
? this.getEntity(objectContext, clazz, id)
: this.getEntity(objectContext, clazz, key, value);
ModelInstance model = new ModelInstance(extConfig, entity);
objectContext.deleteObjects(entity);
objectContext.commitChanges();
return model;
}
/*
* Update
*/
private void updateEntity(ObjectContext objectContext, Class<?> clazz, Object entity, Map<String,Object> data) throws Exception {
CayenneEntityType cayenneEntity = getCayenneEntity(clazz.getSimpleName());
for (String fieldName : data.keySet()) {
if (fieldName.equals(AdaptrexEntityType.ENTITY_ID_NAME)) continue;
Object newValue = data.get(fieldName);
CayenneFieldType adaptrexField = cayenneEntity.getCayenneField(fieldName);
if (adaptrexField != null) {
adaptrexField.setValueFor(entity, ExtTypeFormatter.parse(newValue, adaptrexField.getFieldType()));
continue;
}
CayenneAssociationType cayenneAssociation = (CayenneAssociationType) cayenneEntity.getAssociationByIdField(fieldName);
if (cayenneAssociation != null) {
cayenneAssociation.setById(objectContext, entity, newValue);
continue;
}
CayenneCollectionType cayenneCollection = (CayenneCollectionType) cayenneEntity.getCollectionByIdsField(fieldName);
if (cayenneCollection != null) {
cayenneCollection.setByIds(objectContext, entity, newValue);
}
}
objectContext.commitChanges();
};
}