/************************************************************************ * Copyright (c) 2014-2016 IoT-Solutions e.U. * * 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 iot.jcypher.domain.internal; import java.lang.reflect.Constructor; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import iot.jcypher.concurrency.Locking; import iot.jcypher.concurrency.QExecution; import iot.jcypher.database.DBType; import iot.jcypher.database.IDBAccess; import iot.jcypher.domain.IDomainAccess; import iot.jcypher.domain.IGenericDomainAccess; import iot.jcypher.domain.ResolutionDepth; import iot.jcypher.domain.SyncInfo; import iot.jcypher.domain.genericmodel.DOType; import iot.jcypher.domain.genericmodel.DOTypeBuilderFactory; import iot.jcypher.domain.genericmodel.DomainObject; import iot.jcypher.domain.genericmodel.InternalAccess; import iot.jcypher.domain.genericmodel.internal.DomainModel; import iot.jcypher.domain.internal.SkipLimitCalc.SkipsLimits; import iot.jcypher.domain.mapping.CompoundObjectMapping; import iot.jcypher.domain.mapping.CompoundObjectType; import iot.jcypher.domain.mapping.DefaultObjectMappingCreator; import iot.jcypher.domain.mapping.DomainState; import iot.jcypher.domain.mapping.DomainState.IRelation; import iot.jcypher.domain.mapping.DomainState.KeyedRelation; import iot.jcypher.domain.mapping.DomainState.KeyedRelationToChange; import iot.jcypher.domain.mapping.DomainState.LoadInfo; import iot.jcypher.domain.mapping.DomainState.Relation; import iot.jcypher.domain.mapping.DomainState.RelationLoadInfo; import iot.jcypher.domain.mapping.DomainState.SourceField2TargetKey; import iot.jcypher.domain.mapping.DomainState.SourceFieldKey; import iot.jcypher.domain.mapping.FieldMapping; import iot.jcypher.domain.mapping.FieldMapping.FieldKind; import iot.jcypher.domain.mapping.IMapEntry; import iot.jcypher.domain.mapping.MapTerminator; import iot.jcypher.domain.mapping.MappingUtil; import iot.jcypher.domain.mapping.ObjectMapping; import iot.jcypher.domain.mapping.surrogate.AbstractSurrogate; import iot.jcypher.domain.mapping.surrogate.Array; import iot.jcypher.domain.mapping.surrogate.Deferred2DO; import iot.jcypher.domain.mapping.surrogate.IDeferred; import iot.jcypher.domain.mapping.surrogate.IEntryUpdater; import iot.jcypher.domain.mapping.surrogate.ISurrogate2Entry; import iot.jcypher.domain.mapping.surrogate.InnerClassSurrogate; import iot.jcypher.domain.mapping.surrogate.ListEntriesUpdater; import iot.jcypher.domain.mapping.surrogate.MapEntry; import iot.jcypher.domain.mapping.surrogate.MapEntryUpdater; import iot.jcypher.domain.mapping.surrogate.ObservableList; import iot.jcypher.domain.mapping.surrogate.Surrogate2ListEntry; import iot.jcypher.domain.mapping.surrogate.Surrogate2MapEntry; import iot.jcypher.domainquery.DomainQuery; import iot.jcypher.domainquery.GDomainQuery; import iot.jcypher.domainquery.QueryLoader; import iot.jcypher.domainquery.QueryPersistor; import iot.jcypher.domainquery.internal.QueryRecorder; import iot.jcypher.domainquery.internal.ReplayedQueryContext; import iot.jcypher.graph.GrAccess; import iot.jcypher.graph.GrLabel; import iot.jcypher.graph.GrNode; import iot.jcypher.graph.GrProperty; import iot.jcypher.graph.GrRelation; import iot.jcypher.graph.Graph; import iot.jcypher.graph.internal.LockUtil; import iot.jcypher.query.JcQuery; import iot.jcypher.query.JcQueryResult; import iot.jcypher.query.api.IClause; import iot.jcypher.query.api.pattern.Node; import iot.jcypher.query.factories.clause.CASE; import iot.jcypher.query.factories.clause.DO; import iot.jcypher.query.factories.clause.ELSE; import iot.jcypher.query.factories.clause.END; import iot.jcypher.query.factories.clause.FOR_EACH; import iot.jcypher.query.factories.clause.MATCH; import iot.jcypher.query.factories.clause.MERGE; import iot.jcypher.query.factories.clause.NATIVE; import iot.jcypher.query.factories.clause.ON_CREATE; import iot.jcypher.query.factories.clause.OPTIONAL_MATCH; import iot.jcypher.query.factories.clause.RETURN; import iot.jcypher.query.factories.clause.SEPARATE; import iot.jcypher.query.factories.clause.START; import iot.jcypher.query.factories.clause.WHEN; import iot.jcypher.query.factories.clause.WHERE; import iot.jcypher.query.factories.clause.WITH; import iot.jcypher.query.factories.xpression.C; import iot.jcypher.query.result.JcError; import iot.jcypher.query.result.JcResultException; import iot.jcypher.query.result.util.ResultHandler; import iot.jcypher.query.values.JcNode; import iot.jcypher.query.values.JcNumber; import iot.jcypher.query.values.JcRelation; import iot.jcypher.query.values.JcValue; import iot.jcypher.query.writer.Format; import iot.jcypher.transaction.ITransaction; import iot.jcypher.transaction.internal.AbstractTransaction; import iot.jcypher.util.QueriesPrintObserver.QueryToObserve; import iot.jcypher.util.Util; public class DomainAccess implements IDomainAccess, IIntDomainAccess { private DomainAccessHandler domainAccessHandler; private InternalDomainAccess internalDomainAccess; private GenericDomainAccess genericDomainAccess; private static ThreadLocal<QExecution> qExecution = new ThreadLocal<QExecution>(); /** * @param dbAccess the graph database connection * @param domainName * @param domainLabelUse */ public DomainAccess(IDBAccess dbAccess, String domainName, DomainLabelUse domainLabelUse) { super(); this.domainAccessHandler = new DomainAccessHandler(dbAccess, domainName, domainLabelUse); } @Override public List<JcError> store(Object domainObject) { List<Object> domainObjects = new ArrayList<Object>(); domainObjects.add(domainObject); return this.store(domainObjects); } @Override public List<JcError> store(List<?> domainObjects) { List<JcError> ret = null; String pLab = this.domainAccessHandler.setDomainLabel(); ITransaction txToClose = null; try { if (this.domainAccessHandler.lockingStrategy == Locking.OPTIMISTIC) { if (this.domainAccessHandler.dbAccess.getTX() == null) txToClose = this.beginTX(); } ret = this.domainAccessHandler.store(domainObjects); } finally { CurrentDomain.setDomainLabel(pLab); if (txToClose != null) { // we have created the transaction if (ret == null) ret = new ArrayList<JcError>(); ret.addAll(txToClose.close()); } } return ret; } @Override public <T> T loadById(Class<T> domainObjectClass, int resolutionDepth, long id) { long[] ids = new long[] {id}; List<T> ret = this.loadByIds(domainObjectClass, resolutionDepth, ids); return ret.get(0); } @Override public <T> List<T> loadByIds(Class<T> domainObjectClass, int resolutionDepth, long... ids) { List<T> ret; String pLab = this.domainAccessHandler.setDomainLabel(); try { ret = this.domainAccessHandler.loadByIds(domainObjectClass, null, resolutionDepth, ids); } finally { CurrentDomain.setDomainLabel(pLab); } return ret; } @Override public <T> List<T> loadByType(Class<T> domainObjectClass, int resolutionDepth, int offset, int count) { List<T> ret; String pLab = this.domainAccessHandler.setDomainLabel(); try { ret = this.domainAccessHandler.loadByType(domainObjectClass, resolutionDepth, offset, count); } finally { CurrentDomain.setDomainLabel(pLab); } return ret; } @Override public SyncInfo getSyncInfo(Object domainObject) { List<Object> domainObjects = new ArrayList<Object>(1); domainObjects.add(domainObject); List<SyncInfo> ret = this.domainAccessHandler.getSyncInfos(domainObjects); return ret.get(0); } @Override public List<SyncInfo> getSyncInfos(List<Object> domainObjects) { return this.domainAccessHandler.getSyncInfos(domainObjects); } @Override public long numberOfInstancesOf(Class<?> type) { List<Class<?>> types = new ArrayList<Class<?>>(1); types.add(type); List<Long> ret = this.numberOfInstancesOf(types); return ret.get(0); } @Override public List<Long> numberOfInstancesOf(List<Class<?>> types) { List<Long> ret; String pLab = this.domainAccessHandler.setDomainLabel(); try { ret = this.domainAccessHandler.numberOfInstancesOf(types); } finally { CurrentDomain.setDomainLabel(pLab); } return ret; } @Override public DomainQuery createQuery() { DomainQuery ret = new DomainQuery(this); QueryRecorder.recordCreateQuery(ret); iot.jcypher.domainquery.InternalAccess.recordQuery(ret, QueryRecorder.getRecordedQuery(ret)); return ret; } @Override public List<String> getStoredQueryNames() { return this.domainAccessHandler.getStoredQueryNames(); } @Override public QueryPersistor createQueryPersistor(DomainQuery query) { return iot.jcypher.domainquery.InternalAccess.createQueryPersistor(query, this); } @Override public QueryLoader<DomainQuery> createQueryLoader(String queryName) { return iot.jcypher.domainquery.InternalAccess.createQueryLoader(queryName, this); } private DomainQuery createRecordedQuery(ReplayedQueryContext rqc, boolean doRecord) { DomainQuery ret = new DomainQuery(this); QueryRecorder.recordCreateQuery(ret); if (doRecord) { iot.jcypher.domainquery.InternalAccess.recordQuery(ret, QueryRecorder.getRecordedQuery(ret)); } iot.jcypher.domainquery.InternalAccess.replayQuery(ret, rqc); return ret; } @Override public InternalDomainAccess getInternalDomainAccess() { if (this.internalDomainAccess == null) this.internalDomainAccess = new InternalDomainAccess(); return this.internalDomainAccess; } @Override public ITransaction beginTX() { ITransaction ret = this.domainAccessHandler.dbAccess.beginTX(); ((AbstractTransaction)ret).setIntDomainAccess(this); synchronized (this.domainAccessHandler) { DomainState ds = this.domainAccessHandler.domainState.createCopy(); this.domainAccessHandler.transactionState.set(ds); this.domainAccessHandler.domainModel.beginTx(); } return ret; } @Override public IDomainAccess setLockingStrategy(Locking locking) { this.domainAccessHandler.lockingStrategy = locking; return this; } @Override public IGenericDomainAccess getGenericDomainAccess() { if (this.genericDomainAccess == null) this.genericDomainAccess = new GenericDomainAccess(); return this.genericDomainAccess; } /**********************************************************************/ public class GenericDomainAccess implements IGenericDomainAccess, IIntDomainAccess { @Override public List<JcError> store(DomainObject domainObject) { List<JcError> ret = DomainAccess.this.store(InternalAccess.getRawObject(domainObject)); DomainState ds = domainAccessHandler.getDomainState(); LoadInfo info = ds.getLoadInfoFrom_Object2IdMap(InternalAccess.getRawObject(domainObject)); info.setDomainObject(domainObject); domainAccessHandler.domainModel.removeNurseryObject(InternalAccess.getRawObject(domainObject)); return ret; } @Override public List<JcError> store(List<DomainObject> domainObjects) { List<Object> domObjs = new ArrayList<Object>(domainObjects.size()); for(DomainObject dobj : domainObjects) { domObjs.add(InternalAccess.getRawObject(dobj)); } List<JcError> ret = DomainAccess.this.store(domObjs); DomainState ds = domainAccessHandler.getDomainState(); for(DomainObject dobj : domainObjects) { LoadInfo info = ds.getLoadInfoFrom_Object2IdMap(InternalAccess.getRawObject(dobj)); info.setDomainObject(dobj); domainAccessHandler.domainModel.removeNurseryObject(InternalAccess.getRawObject(dobj)); } return ret; } @Override public List<DomainObject> loadByIds(String domainObjectClassName, int resolutionDepth, long... ids) { List<Object> objs = domainAccessHandler.loadByIds(null, null, resolutionDepth, ids); List<DomainObject> ret = this.getDomainObjects(objs); return ret; } @Override public DomainObject loadById(String domainObjectClassName, int resolutionDepth, long id) { long[] ids = new long[] {id}; List<DomainObject> ret = this.loadByIds(domainObjectClassName, resolutionDepth, ids); return ret.get(0); } @Override public List<DomainObject> loadByType(String domainObjectClassName, int resolutionDepth, int offset, int count) { List<DomainObject> ret; try { domainAccessHandler.updateMappingsIfNeeded(); Class<?> clazz = domainAccessHandler.domainModel.getClassForName(domainObjectClassName); List<?> objs = this.getDomainAccess().loadByType(clazz, resolutionDepth, offset, count); ret = this.getDomainObjects(objs); } catch(Throwable e) { if (e instanceof RuntimeException) throw (RuntimeException)e; else throw new RuntimeException(e); } return ret; } @Override public IDomainAccess getDomainAccess() { return DomainAccess.this; } private List<DomainObject> getDomainObjects(List<?> objects) { List<DomainObject> ret = new ArrayList<>(objects.size()); DomainState ds = domainAccessHandler.getDomainState(); for (Object obj : objects) { LoadInfo lInfo = ds.getLoadInfoFrom_Object2IdMap(obj); DomainObject dObj = lInfo.getDomainObject(); if (dObj == null) { dObj = domainAccessHandler.domainModel.getCreateDomainObjectFor(obj); lInfo.setDomainObject(dObj); } ret.add(dObj); } return ret; } private DomainObject getDomainObject(Object obj) { DomainState ds = domainAccessHandler.getDomainState(); LoadInfo lInfo = ds.getLoadInfoFrom_Object2IdMap(obj); if (lInfo != null) { DomainObject dObj = lInfo.getDomainObject(); if (dObj == null) { dObj = domainAccessHandler.domainModel.getCreateDomainObjectFor(obj); lInfo.setDomainObject(dObj); } return dObj; } return null; } @Override public DOTypeBuilderFactory getTypeBuilderFactory() { return domainAccessHandler.domainModel.getTypeBuilderFactory(); } @Override public DOType getDomainObjectType(String typeName) { domainAccessHandler.updateMappingsIfNeeded(); return domainAccessHandler.domainModel.getDOType(typeName); } @Override public List<SyncInfo> getSyncInfos(List<DomainObject> domainObjects) { List<Object> dobjs = new ArrayList<Object>(domainObjects.size()); for (DomainObject dobj : domainObjects) { dobjs.add(InternalAccess.getRawObject(dobj)); } return DomainAccess.this.getSyncInfos(dobjs); } @Override public SyncInfo getSyncInfo(DomainObject domainObject) { return DomainAccess.this.getSyncInfo(InternalAccess.getRawObject(domainObject)); } @Override public long numberOfInstancesOf(String typeName) { try { domainAccessHandler.updateMappingsIfNeeded(); Class<?> typ = domainAccessHandler.domainModel.getClassForName(typeName); return DomainAccess.this.numberOfInstancesOf(typ); } catch(Throwable e) { if (e instanceof RuntimeException) throw (RuntimeException)e; else throw new RuntimeException(e); } } @Override public List<Long> numberOfInstancesOf(List<String> typeNames) { try { domainAccessHandler.updateMappingsIfNeeded(); List<Class<?>> typs = new ArrayList<Class<?>>(typeNames.size()); for (String tn : typeNames) { Class<?> typ = domainAccessHandler.domainModel.getClassForName(tn); typs.add(typ); } return DomainAccess.this.numberOfInstancesOf(typs); } catch(Throwable e) { if (e instanceof RuntimeException) throw (RuntimeException)e; else throw new RuntimeException(e); } } @Override public GDomainQuery createQuery() { GDomainQuery ret = new GDomainQuery(DomainAccess.this); QueryRecorder.recordCreateQuery(ret); iot.jcypher.domainquery.InternalAccess.recordQuery(ret, QueryRecorder.getRecordedQuery(ret)); return ret; } @Override public List<String> getStoredQueryNames() { return domainAccessHandler.getStoredQueryNames(); } @Override public QueryPersistor createQueryPersistor(GDomainQuery query) { return iot.jcypher.domainquery.InternalAccess.createQueryPersistor(query, this); } @Override public QueryLoader<GDomainQuery> createQueryLoader(String queryName) { return iot.jcypher.domainquery.InternalAccess.createQueryLoader(queryName, this); } private GDomainQuery createRecordedQuery(ReplayedQueryContext rqc, boolean doRecord) { GDomainQuery ret = new GDomainQuery(DomainAccess.this); QueryRecorder.recordCreateQuery(ret); if (doRecord) { iot.jcypher.domainquery.InternalAccess.recordQuery(ret, QueryRecorder.getRecordedQuery(ret)); } iot.jcypher.domainquery.InternalAccess.replayQuery(ret, rqc); return ret; } @Override public ITransaction beginTX() { return DomainAccess.this.beginTX(); } @Override public IGenericDomainAccess setLockingStrategy(Locking locking) { domainAccessHandler.lockingStrategy = locking; return this; } @Override public InternalDomainAccess getInternalDomainAccess() { return DomainAccess.this.getInternalDomainAccess(); } } /**********************************************************************/ public class DomainAccessHandler { private String regexClassfieldSep = "\\".concat(FieldMapping.ClassFieldSeparator); private static final String NodePrefix = "n_"; private static final String RelationPrefix = "r_"; private static final String DomainInfoNodeLabel = "DomainInfo"; private static final String DomainInfoNameProperty = "name"; private static final String DomainInfoLabel2ClassProperty = "label2ClassMap"; private static final String DomainInfoFieldComponentTypeProperty = "componentTypeMap"; private static final String DomainInfoConcreteFieldTypeProperty = "fieldTypeMap"; private static final String DomainInfoUseDomainLabelProperty = "useDomainLabels"; private static final String DomainInfoVersionProperty = "_i_version"; private static final String DomainInfoModelVersionProperty = "_m_version"; private static final String KeyProperty = "key"; private static final String ValueProperty = "value"; private static final String KeyTypeProperty = "keyType"; private static final String ValueTypeProperty = "valueType"; private String domainName; private String domainLabel; /** * defines at which recursion occurrence building a query is stopped */ private int maxRecursionCount = 1; private int maxPathSize = 2; private IDBAccess dbAccess; private DomainState domainState; private ThreadLocal<DomainState> transactionState; private ThreadLocal<ReResolve> reResolve; private Locking lockingStrategy; private Map<Class<?>, ObjectMapping> mappings; // for a root level type in a query, all possible variants (subclasses) must be considered // in order to build a query completely resolving all paths of all possible variants. // That is important, if the root level type is an interface, an abstract class or simple a super class // of the object that has actually been stored in the graph. private Map<Class<?>, CompoundObjectType> type2CompoundTypeMap; private DomainInfo domainInfo; private DomainModel domainModel; private DomainLabelUse domainLabelUse; private DomainAccessHandler(IDBAccess dbAccess, String domainName, DomainLabelUse du) { super(); this.domainLabelUse = du; this.domainName = domainName; this.dbAccess = new DBAccessWrapper(dbAccess); this.domainState = new DomainState(); this.mappings = new HashMap<Class<?>, ObjectMapping>(); this.type2CompoundTypeMap = new HashMap<Class<?>, CompoundObjectType>(); this.transactionState = new ThreadLocal<DomainState>(); this.reResolve = new ThreadLocal<ReResolve>(); this.lockingStrategy = Locking.NONE; this.domainModel = iot.jcypher.domain.genericmodel.internal.InternalAccess .createDomainModel(this.domainName, getDomainLabel(), DomainAccess.this); } @SuppressWarnings("unchecked") <T> List<T> loadByIds(Class<T> domainObjectClass, Map<Class<?>, List<Long>> type2IdsMap, int resolutionDepth, long... ids) { List<T> resultList = new ArrayList<T>(ids.length); if (ids.length == 0) return resultList; InternalDomainAccess internalAccess = null; try { internalAccess = MappingUtil.internalDomainAccess.get(); MappingUtil.internalDomainAccess.set(getInternalDomainAccess()); updateMappingsIfNeeded(); Map<Class<?>, List<Long>> typeMap = type2IdsMap; if (typeMap == null) typeMap = queryConcreteTypes(ids); Iterator<Entry<Class<?>, List<Long>>> it = typeMap.entrySet().iterator(); while(it.hasNext()) { Entry<Class<?>, List<Long>> entry = it.next(); if (domainObjectClass != null) { // null when using generic domain model if (!domainObjectClass.isAssignableFrom(entry.getKey())) throw new RuntimeException("concrete type must be the same or a subtype of: " + domainObjectClass.getName()); } ClosureQueryContext context = new ClosureQueryContext(entry.getKey()); new ClosureCalculator().calculateClosureQuery(context); boolean repeat = context.matchClauses != null && context.matchClauses.size() > 0; List<IdAndDepth> idList = new ArrayList<IdAndDepth>(entry.getValue().size()); for (Long id : entry.getValue()) { idList.add(new IdAndDepth(id, 0)); } if (repeat) { // has one or more match clauses loadByIdsWithMatches(entry.getKey(), context, null, null, idList, resolutionDepth); } else { // only simple start by id clauses are needed loadByIdsSimple(entry.getKey(), idList); } } } catch(Throwable e) { if (!(e instanceof RuntimeException)) throw new RuntimeException(e); else throw e; } finally { if (internalAccess != null) MappingUtil.internalDomainAccess.set(internalAccess); else MappingUtil.internalDomainAccess.remove(); } for (long id : ids) { resultList.add((T)getDomainState().getFrom_Id2ObjectMap(id)); } return resultList; } @SuppressWarnings("rawtypes") void loadDeep(List<FillModelContext.ResolvedDepth> objectsNotResolvedDeep, Set<IDeferred> deferredSet, SurrogateChangeLog surrogateChangeLog, int resolutionDepth) { Map<Class<?>, List<FillModelContext.ResolvedDepth>> byType = new HashMap<Class<?>, List<FillModelContext.ResolvedDepth>>(); for (FillModelContext.ResolvedDepth notResolvedDeep : objectsNotResolvedDeep) { List<FillModelContext.ResolvedDepth> list = byType.get(notResolvedDeep.domainObject.getClass()); if (list == null) { list = new ArrayList<FillModelContext.ResolvedDepth>(); byType.put(notResolvedDeep.domainObject.getClass(), list); } list.add(notResolvedDeep); } Iterator<Entry<Class<?>, List<FillModelContext.ResolvedDepth>>> it = byType.entrySet().iterator(); while(it.hasNext()) { Entry<Class<?>, List<FillModelContext.ResolvedDepth>> entry = it.next(); loadDeepByType(entry.getKey(), entry.getValue(), deferredSet, surrogateChangeLog, resolutionDepth); } } <T> List<T> loadByType(Class<T> domainObjectClass, int resolutionDepth, int offset, int count) { if (offset < 0) throw new RuntimeException("offset must be >= 0"); List<Class<?>> typeList = this.getCompoundTypesFor(domainObjectClass); int numTypes = typeList.size(); JcNode n = new JcNode("n"); JcNumber num = new JcNumber("num"); List<Integer> offsets; List<Integer> lens; if (numTypes > 1 && (offset > 0 || count >= 0)) { List<JcQuery> queries = new ArrayList<JcQuery>(numTypes); for (Class<?> rawType : typeList) { String nodeLabel = domainInfo.getLabelForClass(rawType); if (nodeLabel != null) { JcQuery query = new JcQuery(); query.setClauses(new IClause[]{ MATCH.node(n).label(nodeLabel), RETURN.count().value(n).AS(num) }); queries.add(query); } } // Util.printQueries(queries, "LOAD-BY-TYPE-COUNT", Format.PRETTY_1); List<JcQueryResult> results = this.dbAccess.execute(queries); List<JcError> errors = Util.collectErrors(results); if (errors.size() > 0) { throw new JcResultException(errors); } // Util.printResults(results, "LOAD-BY-TYPE-COUNT", Format.PRETTY_1); List<Integer> counts = new ArrayList<Integer>(results.size()); for (JcQueryResult result : results) { BigDecimal res = result.resultOf(num).get(0); counts.add(res.intValue()); } SkipsLimits slc = SkipLimitCalc.calcSkipsLimits(counts, offset, count); offsets = slc.getOffsets(); lens = slc.getLengths(); } else { offsets = new ArrayList<Integer>(numTypes); lens = new ArrayList<Integer>(numTypes); if (numTypes == 1) { offsets.add(offset); lens.add(count); } else { for (int i = 0; i < numTypes; i++) { offsets.add(0); lens.add(-1); } } } List<JcQuery> queries = new ArrayList<JcQuery>(numTypes); int idx = 0; for (Class<?> rawType : typeList) { String nodeLabel = domainInfo.getLabelForClass(rawType); if (nodeLabel != null) { if (lens.get(idx) != 0) { // otherwise there is a distinct number of elements or -1 JcQuery query = new JcQuery(); query.setClauses(new IClause[]{ MATCH.node(n).label(nodeLabel), RETURN.value(n.id()).AS(num) }); queries.add(query); } idx++; } } List<Long> ids = new ArrayList<Long>(); if (queries.size() > 0) { Util.printQueries(queries, QueryToObserve.LOAD_BY_TYPE_QUERY, Format.PRETTY_1); List<JcQueryResult> results = this.dbAccess.execute(queries); List<JcError> errors = Util.collectErrors(results); if (errors.size() > 0) { throw new JcResultException(errors); } // Util.printResults(results, "LOAD-BY-TYPE", Format.PRETTY_1); idx = 0; int resIdx = 0; for (Class<?> rawType : typeList) { String nodeLabel = domainInfo.getLabelForClass(rawType); if (nodeLabel != null) { if (lens.get(idx) != 0) { // no query in that case JcQueryResult result = results.get(resIdx); List<BigDecimal> rList = result.resultOf(num); int sz = lens.get(idx) + offsets.get(idx); sz = sz == -1 ? rList.size() : sz > rList.size() ? rList.size() : sz; for (int i = offsets.get(idx); i < sz; i++) { ids.add(rList.get(i).longValue()); } resIdx++; } idx++; } } } long[] idsArray = new long[ids.size()]; for (int i = 0; i < ids.size(); i++) { idsArray[i] = ids.get(i).longValue(); } return loadByIds(domainObjectClass, null, resolutionDepth, idsArray); } @SuppressWarnings("rawtypes") <T> void loadDeepByType(Class<T> domainObjectClass, List<FillModelContext.ResolvedDepth> objectsNotResolvedDeep, Set<IDeferred> deferredSet, SurrogateChangeLog surrogateChangeLog, int resolutionDepth) { InternalDomainAccess internalAccess = null; ClosureQueryContext context = new ClosureQueryContext(domainObjectClass); List<IdAndDepth> idList = new ArrayList<IdAndDepth>(objectsNotResolvedDeep.size()); for (int i = 0; i < objectsNotResolvedDeep.size(); i++) { FillModelContext.ResolvedDepth resDepth = objectsNotResolvedDeep.get(i); idList.add(new IdAndDepth(this.getDomainState().getLoadInfoFrom_Object2IdMap( resDepth.domainObject).getId(), resDepth.resolvedDepth)); } try { internalAccess = MappingUtil.internalDomainAccess.get(); MappingUtil.internalDomainAccess.set(getInternalDomainAccess()); updateMappingsIfNeeded(); new ClosureCalculator().calculateClosureQuery(context); boolean repeat = context.matchClauses != null && context.matchClauses.size() > 0; if (repeat) { // has one or more match clauses loadByIdsWithMatches(domainObjectClass, context, deferredSet, surrogateChangeLog, idList, resolutionDepth); } else { // only simple start by id clauses are needed loadByIdsSimple(domainObjectClass, idList); } } catch(Throwable e) { if (!(e instanceof RuntimeException)) throw new RuntimeException(e); else throw e; } finally { if (internalAccess != null) MappingUtil.internalDomainAccess.set(internalAccess); else MappingUtil.internalDomainAccess.remove(); } } List<JcError> store(List<?> domainObjects) { UpdateContext context; InternalDomainAccess internalAccess = null; try { internalAccess = MappingUtil.internalDomainAccess.get(); MappingUtil.internalDomainAccess.set(getInternalDomainAccess()); context = this.updateLocalGraph(domainObjects); } finally { if (internalAccess != null) MappingUtil.internalDomainAccess.set(internalAccess); else MappingUtil.internalDomainAccess.remove(); } List<JcError> errors; if (context.lockingErrors) { // only set if Locking.OPTIMISTIC and there are errors errors = new ArrayList<JcError>(); JcError error = new JcError("JCypher.Locking", "Optimistic locking failed (an element was deleted by another client)", null); errors.add(error); ITransaction tx = this.dbAccess.getTX(); if (tx != null) tx.failure(); } else { Map<Long, Integer> elementVersionsMap = null; if (this.lockingStrategy == Locking.OPTIMISTIC) { if (context.nodeIndexMap != null || context.relationIndexMap != null) elementVersionsMap = new HashMap<Long, Integer>(); if (context.nodeIndexMap != null) { Iterator<QueryNode2ResultNode> it = context.nodeIndexMap.values().iterator(); while (it.hasNext()) { QueryNode2ResultNode n2n = it.next(); elementVersionsMap.put(n2n.resultNode.getId(), n2n.version); } } if (context.relationIndexMap != null) { Iterator<QueryRelation2ResultRelation> it = context.relationIndexMap.values().iterator(); while (it.hasNext()) { QueryRelation2ResultRelation r2r = it.next(); elementVersionsMap.put(r2r.resultRelation.getId(), r2r.version); } } } errors = GrAccess.store(context.graph, elementVersionsMap); DomainState ds = getDomainState(); if (errors.isEmpty()) { for (IRelation relat : context.relationsToRemove) { ds.removeRelation(relat); } Iterator<Entry<Object, GrNode>> it = context.domObj2Node.entrySet().iterator(); while(it.hasNext()) { Entry<Object, GrNode> entry = it.next(); GrNode nd = entry.getValue(); GrProperty prop = nd.getProperty(ResultHandler.lockVersionProperty); int v = -1; if (prop != null) v = ((Number)prop.getValue()).intValue(); ds.add_Id2Object(entry.getKey(), nd.getId(), v, ResolutionDepth.DEEP); } for (DomRelation2ResultRelation d2r : context.domRelation2Relations) { GrRelation rel = d2r.resultRelation; GrProperty prop = rel.getProperty(ResultHandler.lockVersionProperty); int v = -1; if (prop != null) v = ((Number)prop.getValue()).intValue(); ds.add_Id2Relation(d2r.domRelation, rel.getId(), v); } } } return errors; } List<SyncInfo> getSyncInfos(List<Object> domainObjects) { List<SyncInfo> ret = new ArrayList<SyncInfo>(domainObjects.size()); for (Object obj : domainObjects) { LoadInfo li = this.getDomainState().getLoadInfoFrom_Object2IdMap(obj); if (li != null) ret.add(new SyncInfo(li.getId(), li.getResolutionDepth())); else ret.add(new SyncInfo(-1, null)); } return ret; } List<Long> numberOfInstancesOf(List<Class<?>> types) { updateMappingsIfNeeded(); List<Long> resultList = new ArrayList<Long>(); List<Integer> cumulationList = new ArrayList<Integer>(types.size()); List<JcQuery> queries = new ArrayList<JcQuery>(); JcNode n = new JcNode("n"); JcNumber num = new JcNumber("num"); for (Class<?> type : types) { Iterator<CompoundObjectType> it = getCompoundTypeFor(type).typeIterator(); int cumulation = 0; while(it.hasNext()) { cumulation++; Class<?> rawType = it.next().getType(); String nodeLabel = domainInfo.getLabelForClass(rawType); JcQuery query = new JcQuery(); query.setClauses(new IClause[]{ MATCH.node(n).label(nodeLabel), RETURN.count().value(n).AS(num) }); queries.add(query); } cumulationList.add(cumulation); } // Util.printQueries(queries, "INSTANCE-COUNT", Format.PRETTY_1); List<JcQueryResult> results = this.dbAccess.execute(queries); List<JcError> errors = Util.collectErrors(results); if (errors.size() > 0) { throw new JcResultException(errors); } // Util.printResults(results, "INSTANCE-COUNT", Format.PRETTY_1); int idx = 0; for (int i = 0; i< cumulationList.size(); i++) { int cumulation = cumulationList.get(i); long count = 0; while (cumulation > 0) { JcQueryResult result = results.get(idx); BigDecimal res = result.resultOf(num).get(0); count = count + res.longValue(); idx++; cumulation--; } resultList.add(count); } return resultList; } List<String> getStoredQueryNames() { List<String> ret = new ArrayList<String>(); IDBAccess dba = ((DBAccessWrapper)this.dbAccess).delegate; String qLabel = this.getDomainLabel() .concat(QueryPersistor.Q_LABEL_POSTFIX); JcNode n = new JcNode("n"); IClause[] clauses = new IClause[] { MATCH.node(n).label(qLabel), RETURN.value(n) }; JcQuery q = new JcQuery(); q.setClauses(clauses); JcQueryResult result = dba.execute(q); if (result.hasErrors()) { StringBuilder sb = new StringBuilder(); Util.appendErrorList(Util.collectErrors(result), sb); throw new RuntimeException(sb.toString()); } List<GrNode> lgn = result.resultOf(n); for (GrNode rn : lgn) { ret.add(rn.getProperty(QueryPersistor.PROP_NAME).getValue().toString()); } return ret; } private synchronized DomainState getDomainState() { DomainState ds = this.transactionState.get(); if (ds == null) ds = this.domainState; return ds; } private DomainModel getDomainModel() { this.loadDomainInfoIfNeeded(); return domainModel; } private Map<Class<?>, List<Long>> queryConcreteTypes(long[] ids) { JcQuery query = new JcQuery(); JcNode n = new JcNode("n"); IClause[] clauses = new IClause[] { START.node(n).byId(ids), RETURN.value(n) }; query.setClauses(clauses); // TODO check in later versions of neo4j if params work // with embedded and in_memory databases if (this.dbAccess.getDBType() != DBType.REMOTE) query.setExtractParams(false); Util.printQuery(query, QueryToObserve.QUERY_CONCRETE_TYPE, Format.PRETTY_1); JcQueryResult result = dbAccess.execute(query); List<JcError> errors = Util.collectErrors(result); if (errors.size() > 0) { throw new JcResultException(errors); } Map<Class<?>, List<Long>> byType = new HashMap<Class<?>, List<Long>>(); DomainInfo di = loadDomainInfoIfNeeded(); List<GrNode> nodes = result.resultOf(n); for (GrNode node : nodes) { List<GrLabel> labels = node.getLabels(); for (GrLabel label : labels) { String lab = label.getName(); Class<?> typ = di.getClassForLabel(lab); if (typ != null) { List<Long> list = byType.get(typ); if (list == null) { list = new ArrayList<Long>(); byType.put(typ, list); } list.add(node.getId()); break; } } } return byType; } private void updateMappingsIfNeeded() { if (this.domainInfo == null) { loadDomainInfoIfNeeded(); } } private UpdateContext updateLocalGraph(List<?> domainObjects) { UpdateContext context = new UpdateContext(); context.lockingErrors = false; new ClosureCalculator().calculateClosure(domainObjects, context); //int sz = domainState.getSurrogateState().size(); context.surrogateChangeLog.applyChanges(); //int sz1 = domainState.getSurrogateState().size(); Graph graph = null; Object domainObject; List<IClause> clauses = null; List<IClause> removeStartClauses = null; List<IClause> removeClauses = null; DomainState ds = this.getDomainState(); for (int i = 0; i < context.domainObjects.size(); i++) { domainObject = context.domainObjects.get(i); LoadInfo li = ds.getLoadInfoFrom_Object2IdMap(domainObject); Long id = li != null ? li.getId() : null; if (id != null) { // object exists in graphdb JcNode n = new JcNode(NodePrefix.concat(String.valueOf(i))); QueryNode2ResultNode n2n = new QueryNode2ResultNode(); n2n.queryNode = n; n2n.version = li.getVersion(); if (context.nodeIndexMap == null) context.nodeIndexMap = new HashMap<Integer, QueryNode2ResultNode>(); context.nodeIndexMap.put(new Integer(i), n2n); if (clauses == null) clauses = new ArrayList<IClause>(); //clauses.add(START.node(n).byId(id.longValue())); // use OPTIONAL_MATCH to be tolerant for removed elements clauses.add(OPTIONAL_MATCH.node(n)); clauses.add(WHERE.valueOf(n.id()).EQUALS(id.longValue())); } } for (int i = 0; i < context.relations.size(); i++) { IRelation relat = context.relations.get(i); RelationLoadInfo rli = ds.getFrom_Relation2IdMap(relat); Long id = rli != null ? rli.getId() : null; if (id != null) { // relation exists in graphdb JcRelation r = new JcRelation(RelationPrefix.concat(String.valueOf(i))); QueryRelation2ResultRelation r2r = new QueryRelation2ResultRelation(); r2r.queryRelation = r; r2r.version = rli.getVersion(); if (context.relationIndexMap == null) context.relationIndexMap = new HashMap<Integer, QueryRelation2ResultRelation>(); context.relationIndexMap.put(new Integer(i), r2r); //clauses.add(START.relation(r).byId(id.longValue())); // use OPTIONAL_MATCH to be tolerant for removed elements clauses.add(OPTIONAL_MATCH.node().relation(r).out().node()); clauses.add(WHERE.valueOf(r.id()).EQUALS(id.longValue())); } } // relations to remove LockUtil.Removes removes = new LockUtil.Removes(); if (context.relationsToRemove.size() > 0) { removeStartClauses = new ArrayList<IClause>(); removeClauses = new ArrayList<IClause>(); for (int i = 0; i < context.relationsToRemove.size(); i++) { IRelation relat = context.relationsToRemove.get(i); // relation must exist in db RelationLoadInfo rli = ds.getFrom_Relation2IdMap(relat); Long id = rli.getId(); JcRelation r = new JcRelation(RelationPrefix.concat(String.valueOf(i))); //removeStartClauses.add(START.relation(r).byId(id.longValue())); // use OPTIONAL_MATCH to be tolerant for removed elements removeStartClauses.add(OPTIONAL_MATCH.node().relation(r).out().node()); removeStartClauses.add(WHERE.valueOf(r.id()).EQUALS(id.longValue())); if (this.lockingStrategy == Locking.OPTIMISTIC) { LockUtil.calcRemoves(removes, r, rli.getVersion()); } removeClauses.add(DO.DELETE(r)); } } // domain objects to remove if (context.domainObjectsToRemove.size() > 0) { if (removeStartClauses == null) { removeStartClauses = new ArrayList<IClause>(); removeClauses = new ArrayList<IClause>(); } for (int i = 0; i < context.domainObjectsToRemove.size(); i++) { Object dobj = context.domainObjectsToRemove.get(i); // node must exist in db LoadInfo li = ds.getLoadInfoFrom_Object2IdMap(dobj); Long id = li.getId(); JcNode n = new JcNode(NodePrefix.concat(String.valueOf(i))); //removeStartClauses.add(START.node(n).byId(id.longValue())); // use OPTIONAL_MATCH to be tolerant for removed elements removeStartClauses.add(OPTIONAL_MATCH.node(n)); removeStartClauses.add(WHERE.valueOf(n.id()).EQUALS(id.longValue())); if (this.lockingStrategy == Locking.OPTIMISTIC) { LockUtil.calcRemoves(removes, n, li.getVersion()); } removeClauses.add(DO.DELETE(n)); } } if (removes.getWithClauses() != null) { JcNumber nSum = new JcNumber("sum"); removes.getWithClauses().add(WITH.value(removes.getSum()).AS(nSum)); } JcNumber nSum = null; if (clauses != null || removeStartClauses != null) { JcQuery query; List<JcQuery> queries = new ArrayList<JcQuery>(); if (clauses != null) { clauses.add(RETURN.ALL()); IClause[] clausesArray = clauses.toArray(new IClause[clauses.size()]); query = new JcQuery(); query.setClauses(clausesArray); queries.add(query); } if (removeStartClauses != null) { if (removes.getWithClauses() != null) { removeStartClauses.addAll(removes.getWithClauses()); JcValue x = new JcValue("x"); nSum = new JcNumber("sum"); // conditional remove in case of Locking.OPTIONAL IClause clause = FOR_EACH.element(x).IN(C.CREATE(new IClause[]{ CASE.result(), WHEN.valueOf(nSum).EQUALS(removes.getVersionSum()), NATIVE.cypher("[1]"), ELSE.perform(), NATIVE.cypher("[]"), END.caseXpr() })).DO(removeClauses.toArray(new IClause[removeClauses.size()])); removeStartClauses.add(clause); removeStartClauses.add(RETURN.value(nSum)); } else removeStartClauses.addAll(removeClauses); query = new JcQuery(); query.setClauses(removeStartClauses.toArray(new IClause[removeStartClauses.size()])); queries.add(query); } Util.printQueries(queries, QueryToObserve.CLOSURE_QUERY, Format.PRETTY_1); List<JcQueryResult> results = this.dbAccess.execute(queries); List<JcError> errors = Util.collectErrors(results); if (errors.size() > 0) { throw new JcResultException(errors); } // check for locking error if (removeStartClauses != null && this.lockingStrategy == Locking.OPTIMISTIC) { JcQueryResult result = clauses != null ? results.get(1) : results.get(0); List<BigDecimal> versionSums = result.resultOf(nSum); if (versionSums.size() == 0 || versionSums.get(0) == null) context.lockingErrors = true; else if (versionSums.get(0).intValue() != removes.getVersionSum()) context.lockingErrors = true; } if (clauses != null) { JcQueryResult result = results.get(0); graph = result.getGraph(); GrAccess.setDBAccess(this.dbAccess, graph); if (context.nodeIndexMap != null) { Iterator<Entry<Integer, QueryNode2ResultNode>> nit = context.nodeIndexMap.entrySet().iterator(); while (nit.hasNext()) { Entry<Integer, QueryNode2ResultNode> entry = nit.next(); List<GrNode> nds = result.resultOf(entry.getValue().queryNode); GrNode res = null; if (nds.size() > 0) res = nds.get(0); if (this.lockingStrategy == Locking.OPTIMISTIC && res == null) // element has been deleted context.lockingErrors = true; entry.getValue().resultNode = res; } } if (context.relationIndexMap != null) { Iterator<Entry<Integer, QueryRelation2ResultRelation>> rit = context.relationIndexMap.entrySet().iterator(); while (rit.hasNext()) { Entry<Integer, QueryRelation2ResultRelation> entry = rit.next(); List<GrRelation> rels = result.resultOf(entry.getValue().queryRelation); GrRelation res = null; if (rels.size() > 0) res = rels.get(0); if (this.lockingStrategy == Locking.OPTIMISTIC && res == null) // element has been deleted context.lockingErrors = true; entry.getValue().resultRelation = res; } } } } // up to here, objects existing as nodes in the graphdb as well as relations have been loaded // and relations that should be removed have been removed from the db if (graph == null) // no nodes loaded from db graph = Graph.create(this.dbAccess); graph.setLockingStrategy(this.lockingStrategy); if (!context.lockingErrors) { context.domObj2Node = new HashMap<Object, GrNode>( context.domainObjects.size()); context.domRelation2Relations = new ArrayList<DomRelation2ResultRelation>(); for (int i = 0; i < context.domainObjects.size(); i++) { GrNode rNode = null; if (context.nodeIndexMap != null && context.nodeIndexMap.get(i) != null) { rNode = context.nodeIndexMap.get(i).resultNode; } if (rNode == null) rNode = graph.createNode(); context.domObj2Node.put(context.domainObjects.get(i), rNode); updateGraphFromObject(context.domainObjects.get(i), rNode); } for (int i = 0; i < context.relations.size(); i++) { GrRelation rRelation = null; if (context.relationIndexMap != null && context.relationIndexMap.get(i) != null) { rRelation = context.relationIndexMap.get(i).resultRelation; } if (rRelation == null) { IRelation relat = context.relations.get(i); rRelation = graph.createRelation(relat.getType(), context.domObj2Node.get(relat.getStart()), context.domObj2Node.get(relat.getEnd())); DomRelation2ResultRelation d2r = new DomRelation2ResultRelation(); d2r.domRelation = relat; d2r.resultRelation = rRelation; context.domRelation2Relations.add(d2r); } updateGraphFromRelation(context.relations.get(i), rRelation); } } context.graph = graph; return context; } /** * has one or more match clauses * @param domainObjectClass * @param context * @param ids * @return */ private <T> List<T> loadByIdsWithMatches(Class<T> domainObjectClass, ClosureQueryContext context, Set<IDeferred> deferredSet, SurrogateChangeLog surrogateChangeLog, List<IdAndDepth> idList, int resolutionDepth) { List<T> resultList = new ArrayList<T>(); Set<IDeferred> deferreds = deferredSet; JcQuery query; String nm = NodePrefix.concat(String.valueOf(0)); List<JcQuery> queries = new ArrayList<JcQuery>(); Map<Long, JcQueryResult> id2QueryResult = new HashMap<Long, JcQueryResult>(); List<Long> queryIds = new ArrayList<Long>(); for (int i = 0; i < idList.size(); i++) { // if the object has already been loaded it will be reloaded (updated) long id = idList.get(i).id; query = new JcQuery(); JcNode n = new JcNode(nm); List<IClause> clauses = new ArrayList<IClause>(); clauses.add(START.node(n).byId(id)); clauses.addAll(context.matchClauses); clauses.add(RETURN.ALL()); IClause[] clausesArray = clauses.toArray(new IClause[clauses.size()]); query.setClauses(clausesArray); queries.add(query); queryIds.add(id); } if (queries.size() > 0) { // at least one node has to be (re)loaded Util.printQueries(queries, QueryToObserve.CLOSURE_QUERY, Format.PRETTY_1); List<JcQueryResult> results = this.dbAccess.execute(queries); List<JcError> errors = Util.collectErrors(results); if (errors.size() > 0) { throw new JcResultException(errors); } // Util.printResults(results, "CLOSURE", Format.PRETTY_1); for (int i = 0; i < queries.size(); i++) { id2QueryResult.put(queryIds.get(i), results.get(i)); } } boolean isRoot; if (deferreds == null) { isRoot = true; deferreds = new HashSet<IDeferred>(); surrogateChangeLog = new SurrogateChangeLog(); } else isRoot = false; @SuppressWarnings("rawtypes") List<FillModelContext.ResolvedDepth> objectsNotResolvedDeep = new ArrayList<FillModelContext.ResolvedDepth>(); for (int i = 0; i < idList.size(); i++) { FillModelContext<T> fContext = new FillModelContext<T>(domainObjectClass, id2QueryResult.get(idList.get(i).id), context.queryEndNodes, context.recursionExitNodes, surrogateChangeLog, resolutionDepth, idList.get(i).depth); new ClosureCalculator().fillModel(fContext); objectsNotResolvedDeep.addAll(fContext.recursionExitObjects); resultList.add(fContext.domainObject); deferreds.addAll(fContext.deferredList); } if (!objectsNotResolvedDeep.isEmpty()) { loadDeep(objectsNotResolvedDeep, deferreds, surrogateChangeLog, resolutionDepth); } if (isRoot) { buildDeferredResolutionTree(deferreds); handleLoops(deferreds); // find leafs List<IDeferred> leafs = new ArrayList<IDeferred>(); for (IDeferred deferred : deferreds) { if (deferred.isLeaf()) leafs.add(deferred); } // handle deferred updates resolveDeferreds(leafs.iterator()); surrogateChangeLog.applyChanges(); } return resultList; } private void handleLoops(Set<IDeferred> deferreds) { for (IDeferred deferred : deferreds) { if (deferred.isRoot()) { deferred.breakLoops(); } } } private void resolveDeferreds(Iterator<IDeferred> it) { while(it.hasNext()) { IDeferred deferred = it.next(); if (deferred.isLeaf()) { deferred.performUpdate(); resolveDeferreds(deferred.nextUp()); } } } private void buildDeferredResolutionTree(Set<IDeferred> deferreds) { for (IDeferred deferred : deferreds) { if (deferred instanceof ISurrogate2Entry) { for (IDeferred def : deferreds) { if (def instanceof IEntryUpdater) { if (((ISurrogate2Entry) deferred).entry2Update().equals(((IEntryUpdater) def).entry2Update())) { deferred.addNextUpInTree(def); } } } } else if (deferred instanceof IEntryUpdater) { for (IDeferred def : deferreds) { if (def instanceof ISurrogate2Entry) { if (((IEntryUpdater) deferred).objectToUpdate() == ((ISurrogate2Entry) def).getSurrogate().objectToUpdate()) { deferred.addNextUpInTree(def); } } else if (def instanceof Deferred2DO) { if (((IEntryUpdater) deferred).objectToUpdate() == ((Deferred2DO) def).getDeferred().objectToUpdate()) { deferred.addNextUpInTree(def); } } } } } } /** * has start by id clauses only * @param domainObjectClass * @param ids * @return */ @SuppressWarnings("unchecked") private <T> List<T> loadByIdsSimple(Class<T> domainObjectClass, List<IdAndDepth> idList) { List<T> resultList = new ArrayList<T>(); List<IClause> clauses = new ArrayList<IClause>(); Map<Long, JcNode> id2QueryNode = new HashMap<Long, JcNode>(); Map<Long, T> id2Object = new HashMap<Long, T>(); DomainState ds = this.getDomainState(); for (int i = 0; i < idList.size(); i++) { long id = idList.get(i).id; // check if domain objects have already been loaded T obj = (T) ds.getFrom_Id2ObjectMap(id); if (obj != null) id2Object.put(id, obj); // if the object has already been loaded it will be reloaded (updated) JcNode n = new JcNode(NodePrefix.concat(String.valueOf(i))); id2QueryNode.put(id, n); clauses.add(START.node(n).byId(id)); } JcQueryResult result = null; if (clauses.size() > 0) { // one or more nodes are to be (re)loaded clauses.add(RETURN.ALL()); JcQuery query = new JcQuery(); IClause[] clausesArray = clauses.toArray(new IClause[clauses.size()]); query.setClauses(clausesArray); result = this.dbAccess.execute(query); if (result.hasErrors()) { List<JcError> errors = Util.collectErrors(result); throw new JcResultException(errors); } } for (int i = 0; i < idList.size(); i++) { long id = idList.get(i).id; T obj = id2Object.get(id); GrNode rNode = result.resultOf(id2QueryNode.get(id)).get(0); T resObj = createIfNeeded_MapProperties(domainObjectClass, rNode, obj); GrProperty prop = rNode.getProperty(ResultHandler.lockVersionProperty); int v = -1; if (prop != null) v = ((Number)prop.getValue()).intValue(); if (obj == null) ds.add_Id2Object(resObj, id, v, ResolutionDepth.DEEP); else ds.getLoadInfoFrom_Object2IdMap(obj) .setResolutionDepth(ResolutionDepth.DEEP) .setVersion(v); resultList.add(resObj); } return resultList; } @SuppressWarnings("unchecked") private <T> T createIfNeeded_MapProperties(Class<T> domainObjectClass, GrNode rNode, T domObj) { T domainObject = domObj; if (domainObject == null) { Class<? extends T> concreteClass; Class<?> clazz = findClassToInstantiateFor(rNode); if (clazz != null) { if (!domainObjectClass.isAssignableFrom(clazz)) { throw new RuntimeException(clazz.getName() + " cannot be assigned to domain object class: " + domainObjectClass.getName()); } else { concreteClass = (Class<? extends T>) clazz; } } else { throw new RuntimeException("node with label(s): " + rNode.getLabels() + " cannot be mapped to domain object class: " + domainObjectClass.getName()); } domainObject = (T) createInstance(concreteClass); } ObjectMapping objectMapping = getObjectMappingFor(domainObject); objectMapping.mapPropertiesToObject(domainObject, rNode); return domainObject; } private Class<?> findClassToInstantiateFor(GrNode rNode) { Iterator<GrLabel> it = rNode.getLabels().iterator(); while (it.hasNext()) { Class<?> clazz = this.domainInfo.getClassForLabel(it.next().getName()); if (clazz != null) { return clazz; } } return null; } private void updateGraphFromObject(Object domainObject, GrNode rNode) { // a mapping for this concrete class has definitly been stored earlier ObjectMapping objectMapping = mappings.get(domainObject.getClass()); objectMapping.mapPropertiesFromObject(domainObject, rNode); } private void updateGraphFromRelation(IRelation relat, GrRelation rRelation) { Object key = null; Object value = null; if (relat instanceof KeyedRelation) { key = ((KeyedRelation)relat).getKey(); value = ((KeyedRelation)relat).getValue(); } else if (relat instanceof KeyedRelationToChange) { key = ((KeyedRelationToChange)relat).getNewOne().getKey(); value = ((KeyedRelationToChange)relat).getNewOne().getValue(); } if (key != null) { GrProperty prop = rRelation.getProperty(KeyProperty); if (prop != null) { Object propValue = MappingUtil.convertFromProperty(prop.getValue(), key.getClass()); if (!key.equals(propValue)) prop.setValue(key); } else rRelation.addProperty(KeyProperty, key); prop = rRelation.getProperty(KeyTypeProperty); if (prop != null) { Object propValue = key.getClass().getName(); if (!prop.getValue().equals(propValue)) prop.setValue(propValue); } else rRelation.addProperty(KeyTypeProperty, key.getClass().getName()); } GrProperty prop = rRelation.getProperty(ValueProperty); if (value != null) { if (prop != null) { Object propValue = MappingUtil.convertFromProperty(prop.getValue(), value.getClass()); if (!value.equals(propValue)) prop.setValue(value); } else rRelation.addProperty(ValueProperty, value); prop = rRelation.getProperty(ValueTypeProperty); if (prop != null) { Object propValue = value.getClass().getName(); if (!prop.getValue().equals(propValue)) prop.setValue(propValue); } else rRelation.addProperty(ValueTypeProperty, value.getClass().getName()); } else { if (prop != null) prop.setValue(null); prop = rRelation.getProperty(ValueTypeProperty); if (prop != null) prop.setValue(null); } } private ObjectMapping getCompoundObjectMappingFor(CompoundObjectType cType, Object filter) { return new CompoundObjectMapping(cType, this.mappings, filter); } private ObjectMapping getObjectMappingFor(Object domainObject) { Class<?> clazz = domainObject.getClass(); return getObjectMappingFor(clazz); } private ObjectMapping getObjectMappingFor(Class<?> clazz) { ObjectMapping objectMapping = this.mappings.get(clazz); if (objectMapping == null) { objectMapping = createObjectMappingFor(clazz); addObjectMappingForClass(clazz, objectMapping); } return objectMapping; } private ObjectMapping createObjectMappingFor(Class<?> clazz) { ObjectMapping ret; InternalDomainAccess internalAccess = null; try { internalAccess = MappingUtil.internalDomainAccess.get(); MappingUtil.internalDomainAccess.set(getInternalDomainAccess()); ret = DefaultObjectMappingCreator.createObjectMapping(clazz, this.domainModel); } finally { if (internalAccess != null) MappingUtil.internalDomainAccess.set(internalAccess); else MappingUtil.internalDomainAccess.remove(); } return ret; } private FieldMapping modifyFieldMapping(FieldMapping fm, FieldMapping parentField) { // Class<?> clazz = fm.getField().getDeclaringClass(); // if (clazz.equals(MapEntry.class) || clazz.equals(iot.jcypher.domain.mapping.Map.class)) { // FieldMappingWithParent mfm = modifiedFieldMappings.get(fm); // if (mfm == null) { // mfm = new FieldMappingWithParent(fm, parentField); // modifiedFieldMappings.put(fm, mfm); // } else // mfm.addParentField(parentField); // return mfm; // } return fm; } private void addObjectMappingForClass(Class<?> domainObjectClass, ObjectMapping objectMapping) { this.mappings.put(domainObjectClass, objectMapping); this.updateCompoundTypeMapWith(domainObjectClass); getAvailableDomainInfo().addClassLabel(domainObjectClass, objectMapping.getNodeLabelMapping().getLabel()); } private DomainInfo loadDomainInfoIfNeeded() { DomainInfo ret; Object so = getInternalDomainAccess().getSyncObject(); if (so != null) { synchronized (so) { ret = intLoadDomainInfoIfNeeded(); } } else ret = intLoadDomainInfoIfNeeded(); return ret; } private DomainInfo intLoadDomainInfoIfNeeded() { if (this.domainInfo == null) { ExecContext ctxt = new ExecContext(); JcQuery query = ((DBAccessWrapper)this.dbAccess).createDomainInfoSyncQuery(ctxt); JcQueryResult result = ((DBAccessWrapper)this.dbAccess) .delegate.execute(query); List<JcError> errors = Util.collectErrors(result); if (errors.isEmpty()) { // Util.printResult(result, "DOMAIN INFO", Format.PRETTY_1); ((DBAccessWrapper)this.dbAccess) .updateDomainInfo(result, ctxt); } else throw new JcResultException(errors); updateClassMapping(this.domainInfo); } return this.domainInfo; } private void updateClassMapping(DomainInfo di) { // update the type2CompoundTypeMap Set<Class<?>> classes = di.getAllStoredDomainClasses(); Iterator<Class<?>> it = classes.iterator(); while(it.hasNext()) { Class<?> clazz = it.next(); ObjectMapping objectMapping = this.mappings.get(clazz); if (objectMapping == null) { objectMapping = createObjectMappingFor(clazz); this.mappings.put(clazz, objectMapping); this.updateCompoundTypeMapWith(clazz); } } } private DomainInfo getAvailableDomainInfo() { DomainInfo ret; if (this.domainInfo != null) ret = this.domainInfo; else { ret = ((DBAccessWrapper)this.dbAccess).temporaryDomainInfo; if (ret == null) { ret = new DomainInfo(-1); ((DBAccessWrapper)this.dbAccess).temporaryDomainInfo = ret; } } return ret; } private String setDomainLabel() { boolean useLab; if (this.domainInfo == null) { if (this.domainLabelUse == DomainLabelUse.AUTO) { // useDomainLabels is set correctly from db useLab = this.loadDomainInfoIfNeeded().useDomainLabels; } else { useLab = this.domainLabelUse == DomainLabelUse.ALWAYS; } } else useLab = this.domainInfo.useDomainLabels; String ret = CurrentDomain.label.get(); if (useLab) CurrentDomain.setDomainLabel(this.getDomainLabel()); return ret; } private String getDomainLabel() { if (this.domainLabel == null) { this.domainLabel = getInternalDomainAccess().buildDomainLabel(this.domainName); } return this.domainLabel; } private CompoundObjectType getCompoundTypeFor(Class<?> clazz) { CompoundObjectType cType = this.type2CompoundTypeMap.get(clazz); if (cType == null) { cType = new CompoundObjectType(clazz); Iterator<Class<?>> it = this.mappings.keySet().iterator(); while(it.hasNext()) { Class<?> typ = it.next(); if (clazz.isAssignableFrom(typ)) cType.addType(typ); } this.type2CompoundTypeMap.put(clazz, cType); } return cType; } private void updateCompoundTypeMapWith(Class<?> clazz) { Iterator<Entry<Class<?>, CompoundObjectType>> it = this.type2CompoundTypeMap.entrySet().iterator(); while(it.hasNext()) { Entry<Class<?>, CompoundObjectType> entry = it.next(); if (entry.getKey().isAssignableFrom(clazz)) entry.getValue().addType(clazz); } } private Object createInstance(Class<?> clazz) { Object ret = null; try { if (clazz.isMemberClass()) { Class<?> eClass = clazz.getEnclosingClass(); Constructor<?> constr = null; Constructor<?>[] constrs = clazz.getDeclaredConstructors(); for (Constructor<?> c : constrs) { Class<?>[] pTypes = c.getParameterTypes(); if (pTypes.length == 1 && pTypes[0].equals(eClass)) { constr = c; break; } } if (constr != null) // inner class, non static ret = new InnerClassSurrogate(constr); } if (ret == null) ret = clazz.newInstance(); } catch(Throwable e) { throw new RuntimeException(e); } return ret; } private List<Class<?>> getCompoundTypesFor(Class<?> domainObjectType) { updateMappingsIfNeeded(); CompoundObjectType cType = getCompoundTypeFor(domainObjectType); List<Class<?>> typeList = cType.getTypes(true); // no abstract types and interfaces // make sure we have always the same order Collections.sort(typeList, new Comparator<Class<?>>() { @Override public int compare(Class<?> o1, Class<?> o2) { return o1.getName().compareTo(o2.getName()); } }); return typeList; } /****************************************/ private class IdAndDepth { private long id; private int depth; private IdAndDepth(long id, int depth) { super(); this.id = id; this.depth = depth; } } /****************************************/ public class DBAccessWrapper implements IDBAccess { private IDBAccess delegate; private DomainInfo temporaryDomainInfo; private DBAccessWrapper(IDBAccess delegate) { super(); this.delegate = delegate; } @Override public JcQueryResult execute(JcQuery query) { ExecContext ctxt = new ExecContext(); QExecution qExec = DomainAccess.qExecution.get(); JcQuery infoQuery = createDomainInfoSyncQuery(ctxt); if (infoQuery != null) { List<JcQuery> queries = new ArrayList<JcQuery>(2); queries.add(query); queries.add(infoQuery); Util.printQueries(queries, QueryToObserve.DOMAINACCESS_EXECUTE_INTERNAL, Format.PRETTY_1); List<JcQueryResult> results = this.delegate.execute(queries); List<JcError> errors = Util.collectErrors(results); if (errors.isEmpty()) { updateDomainInfo(results.get(1), ctxt); } if (qExec != null) qExec.setCheckedReloadModel(true); return results.get(0); } else { List<JcQuery> queries = new ArrayList<JcQuery>(); queries.add(query); boolean doCheck = false; if (qExec != null && qExec.geExecType().shouldCheckForReload() && !qExec.isCheckedReloadModel()) { int[] versions = new int[]{domainAccessHandler.domainInfo.version, domainAccessHandler.domainModel.getVersion()}; queries.add(createCheckInfoVersionQuery(versions)); doCheck = true; } Util.printQueries(queries, QueryToObserve.DOMAINACCESS_EXECUTE_INTERNAL, Format.PRETTY_1); List<JcQueryResult> results = this.delegate.execute(queries); if (doCheck) { // check for need of reload if (Util.collectErrors(results).isEmpty()) { boolean reloaded = reloadInfo_ModelIfNeeded(results.get(1)); if (reloaded) { // need to replay query JcQueryResult r = results.get(0); JcError err = new JcError(QExecution.REPLAY_QUERY, null, null); r.addGeneralError(err); } } } return results.get(0); } } @Override public List<JcQueryResult> execute(List<JcQuery> queries) { ExecContext ctxt = new ExecContext(); QExecution qExec = DomainAccess.qExecution.get(); JcQuery infoQuery = createDomainInfoSyncQuery(ctxt); if (infoQuery != null) { List<JcQuery> extQueries = new ArrayList<JcQuery>(queries.size() + 1); extQueries.addAll(queries); extQueries.add(infoQuery); Util.printQueries(extQueries, QueryToObserve.DOMAINACCESS_EXECUTE_INTERNAL, Format.PRETTY_1); List<JcQueryResult> results = this.delegate.execute(extQueries); // Util.printResults(results, "DOMAIN INFO", Format.PRETTY_1); List<JcError> errors = Util.collectErrors(results); if (errors.isEmpty()) { updateDomainInfo(results.get(queries.size()), ctxt); } if (qExec != null) qExec.setCheckedReloadModel(true); return results.subList(0, queries.size()); } else { boolean doCheck = false; List<JcQuery> extQueries; if (qExec != null && qExec.geExecType().shouldCheckForReload() && !qExec.isCheckedReloadModel()) { int[] versions = new int[]{domainAccessHandler.domainInfo.version, domainAccessHandler.domainModel.getVersion()}; extQueries = new ArrayList<JcQuery>(queries.size() + 1); extQueries.addAll(queries); extQueries.add(createCheckInfoVersionQuery(versions)); doCheck = true; } else extQueries = queries; Util.printQueries(extQueries, QueryToObserve.DOMAINACCESS_EXECUTE_INTERNAL, Format.PRETTY_1); List<JcQueryResult> results = this.delegate.execute(extQueries); if (doCheck) { // check for need of reload if (Util.collectErrors(results).isEmpty()) { boolean reloaded = reloadInfo_ModelIfNeeded(results.get(results.size() - 1)); if (reloaded) { // need to replay query JcQueryResult r = results.get(0); JcError err = new JcError(QExecution.REPLAY_QUERY, null, null); r.addGeneralError(err); } } } return results.subList(0, queries.size()); } } @Override public List<JcError> clearDatabase() { return this.delegate.clearDatabase(); } @Override public boolean isDatabaseEmpty() { return this.delegate.isDatabaseEmpty(); } @Override public ITransaction beginTX() { return this.delegate.beginTX(); } @Override public ITransaction getTX() { return this.delegate.getTX(); } @Override public DBType getDBType() { return this.delegate.getDBType(); } @Override public void close() { this.delegate.close(); } public IDBAccess getDelegate() { return delegate; } private void updateDomainInfo(JcQueryResult result, ExecContext context) { if (context.dInfo == Exec.INIT_LOADED) { // initial load JcNode mdl = new JcNode("mdl"); List<GrNode> mdlInfos = result.resultOf(mdl); domainModel.loadFrom(mdlInfos); JcNode info = new JcNode("info"); List<GrNode> rInfos = result.resultOf(info); DomainInfo dInfo; GrNode rInfo; rInfo = rInfos.get(0); dInfo = new DomainInfo(rInfo.getId()); dInfo.initFrom(rInfo); DomainAccessHandler.this.domainInfo = dInfo; GrProperty prop = rInfo.getProperty(DomainInfoUseDomainLabelProperty); if (prop == null) { // domain info was newly created in graph // AUTO is the default setting if (DomainAccessHandler.this.domainLabelUse == DomainLabelUse.AUTO) { JcNumber infos = new JcNumber("infos"); List<BigDecimal> cInfos = result.resultOf(infos); // set to true if there exist other domain info nodes DomainAccessHandler.this.domainInfo.useDomainLabels = cInfos.get(0).intValue() > 1; } else DomainAccessHandler.this.domainInfo.useDomainLabels = DomainAccessHandler.this.domainLabelUse == DomainLabelUse.ALWAYS; // the default is false, set changed only if default was changed if (DomainAccessHandler.this.domainInfo.useDomainLabels) DomainAccessHandler.this.domainInfo.setChanged(true); } } else if (context.dInfo == Exec.TRIED_STORE || context.dModel == Exec.TRIED_STORE) { // update info to graph JcNumber rDi = new JcNumber("retDi"); JcNumber rDm = new JcNumber("retDm"); int retDi = ((Number)result.resultOf(rDi).get(0)).intValue(); int retDm = ((Number)result.resultOf(rDm).get(0)).intValue(); if (retDi + retDm == 0) { // successfully stored if (context.dInfo == Exec.TRIED_STORE) DomainAccessHandler.this.domainInfo.graphUdated(); if (context.dModel == Exec.TRIED_STORE) { int idx = 0; for (DOType t : DomainAccessHandler.this.domainModel.getUnsaved()) { JcNumber nid = new JcNumber("nid_".concat(String.valueOf(idx))); BigDecimal rNid = result.resultOf(nid).get(0); InternalAccess.setNodeId(t, rNid.longValue()); idx++; } domainModel.updatedToGraph(); } } else { domainModel.updatedToGraph(); // model was stored in any case ExecContext ctxt = new ExecContext(); if (retDi != 0) // domain info was concurrently changed ctxt.dInfo = Exec.RELOAD_STORE; if (retDm != 0) // domain model was concurrently changed ctxt.dModel = Exec.RELOAD_STORE; this.handleDomainInfo_Model(ctxt); // reload and merge } } else if (context.dInfo == Exec.RELOADED_STORE || context.dModel == Exec.RELOADED_STORE) { // merge ExecContext ctxt = new ExecContext(); // need to load the model first (important for generic model) int v = 0; if (context.dModel == Exec.RELOADED_STORE) { // model reloaded after concurrent change JcNode mdl = new JcNode("mdl"); List<GrNode> mdlInfos = result.resultOf(mdl); domainModel.mergeFrom(mdlInfos); // get stored model version if (context.dInfo != Exec.RELOADED_STORE) { JcNumber m_version = new JcNumber("mv"); v = result.resultOf(m_version).get(0).intValue(); } else { JcNode info = new JcNode("info"); v = ((Number)result.resultOf(info).get(0).getProperty(DomainInfoModelVersionProperty).getValue()).intValue(); } // we have stored additional model elements // and need to increment the stored model version by 1 v++; } if (context.dInfo == Exec.RELOADED_STORE) { // domain info reloaded after concurrent change JcNode info = new JcNode("info"); GrNode rInfo = result.resultOf(info).get(0); DomainInfo di = new DomainInfo(domainInfo.nodeId); di.initFrom(rInfo); domainInfo.updateFrom(di); domainInfo.version = di.version + 1; domainInfo.changed = true; updateClassMapping(domainInfo); } else ctxt.dInfo = Exec.STORE_VERSIONS; if (context.dModel == Exec.RELOADED_STORE) domainModel.setVersion(v); this.handleDomainInfo_Model(ctxt); // we still have to store our own changes (if there are any) } else if (context.dInfo == Exec.RELOADED || context.dModel == Exec.RELOADED) { // merge // need to load the model first (important for generic model) if (context.dModel == Exec.RELOADED) { // model reloaded after concurrent change JcNode mdl = new JcNode("mdl"); List<GrNode> mdlInfos = result.resultOf(mdl); domainModel.mergeFrom(mdlInfos); } if (context.dInfo == Exec.RELOADED) { // domain info reloaded after concurrent change JcNode info = new JcNode("info"); GrNode rInfo = result.resultOf(info).get(0); DomainInfo di = new DomainInfo(domainInfo.nodeId); di.initFrom(rInfo); domainInfo.updateFrom(di); domainInfo.version = di.version; domainInfo.changed = false; updateClassMapping(domainInfo); } } if (this.temporaryDomainInfo != null) { DomainAccessHandler.this.domainInfo.updateFrom(this.temporaryDomainInfo); this.temporaryDomainInfo = null; } this.handleDomainInfo_Model(null); // make sure that still pending changes are committed to the database } private JcQuery createDomainInfoSyncQuery(ExecContext context) { JcQuery query = null; String pLab = CurrentDomain.label.get(); CurrentDomain.setDomainLabel(null); try { if (DomainAccessHandler.this.domainInfo == null) { // initial load JcNode info = new JcNode("info"); JcNode infos = new JcNode("infs"); JcNode mdl = new JcNode("mdl"); query = new JcQuery(); query.setClauses(new IClause[] { MERGE.node(info).label(DomainInfoNodeLabel) .property(DomainInfoNameProperty).value(DomainAccessHandler.this.domainName), ON_CREATE.SET(info.property(DomainInfoVersionProperty)).to(0), ON_CREATE.SET(info.property(DomainInfoModelVersionProperty)).to(0), WITH.value(info), OPTIONAL_MATCH.node(infos).label(DomainInfoNodeLabel), SEPARATE.nextClause(), OPTIONAL_MATCH.node(mdl).label(domainModel.getTypeNodeName()), RETURN.value(info), RETURN.value(mdl), RETURN.count().value(infos).AS(new JcNumber("infos")) }); context.dInfo = Exec.INIT_LOADED; context.dModel = Exec.INIT_LOADED; } else if (context.dInfo == Exec.RELOAD || context.dModel == Exec.RELOAD) { // reload List<IClause> clauses = new ArrayList<IClause>(); List<IClause> returnClauses = new ArrayList<IClause>(); JcNode info = new JcNode("info"); if (context.dInfo == Exec.RELOAD) { clauses.add(MERGE.node(info).label(DomainInfoNodeLabel) .property(DomainInfoNameProperty).value(DomainAccessHandler.this.domainName)); clauses.add(ON_CREATE.SET(info.property(DomainInfoVersionProperty)).to(0)); clauses.add(ON_CREATE.SET(info.property(DomainInfoModelVersionProperty)).to(0)); returnClauses.add(RETURN.value(info)); context.dInfo = Exec.RELOADED; } if (context.dModel == Exec.RELOAD) { JcNode mdl = new JcNode("mdl"); if (context.dInfo == Exec.RELOADED) clauses.add(WITH.value(info)); clauses.add(OPTIONAL_MATCH.node(mdl).label(domainModel.getTypeNodeName())); returnClauses.add(RETURN.value(mdl)); context.dModel = Exec.RELOADED; } clauses.addAll(returnClauses); query = new JcQuery(); query.setClauses(clauses.toArray(new IClause[clauses.size()])); } else if (context.dInfo == Exec.RELOAD_STORE || context.dModel == Exec.RELOAD_STORE) { // reload and merge List<IClause> clauses = new ArrayList<IClause>(); List<IClause> returnClauses = new ArrayList<IClause>(); if (context.dInfo == Exec.RELOAD_STORE) { JcNode info = new JcNode("info"); clauses.addAll(createDomainInfoStartClause(info)); clauses.add(WITH.value(info)); // only needed with MERGE as start clause returnClauses.add(RETURN.value(info)); context.dInfo = Exec.RELOADED_STORE; } if (context.dModel == Exec.RELOAD_STORE) { JcNode mdl = new JcNode("mdl"); clauses.add(OPTIONAL_MATCH.node(mdl).label(domainModel.getTypeNodeName())); returnClauses.add(RETURN.value(mdl)); if (context.dInfo != Exec.RELOADED_STORE) { // need to know the stored model version JcNode info = new JcNode("info"); JcNumber m_version = new JcNumber("mv"); clauses.add(0, WITH.value(info)); // only needed with MERGE as start clause clauses.addAll(0, createDomainInfoStartClause(info)); returnClauses.add(RETURN.value(info.property(DomainInfoModelVersionProperty)).AS(m_version)); } context.dModel = Exec.RELOADED_STORE; } clauses.addAll(returnClauses); query = new JcQuery(); query.setClauses(clauses.toArray(new IClause[clauses.size()])); } else if (context.dInfo == Exec.STORE_VERSIONS) { // only store version properties in domain info List<IClause> diClauses = new ArrayList<IClause>(); List<IClause> clauses = new ArrayList<IClause>(); List<IClause> diReturn = new ArrayList<IClause>(); int[] versions = createDomainInfoStoreClauses(diClauses); // build return info: 0 ... OK, 1 ... ERROR (info was concurrently changed) createReturnCodeClauses(clauses, diReturn, versions); // add domain info clauses as second because they change // the stored version properties when they write to the db clauses.addAll(diClauses); // add return clauses clauses.addAll(diReturn); query = new JcQuery(); query.setClauses(clauses.toArray(new IClause[clauses.size()])); context.dInfo = Exec.TRIED_STORE; } else if (DomainAccessHandler.this.domainInfo.isChanged() || DomainAccessHandler.this.domainModel.hasChanged()) { // update info to graph List<IClause> diClauses = new ArrayList<IClause>(); List<IClause> diReturn = new ArrayList<IClause>(); List<IClause>[] dmClauses = null; int[] versions = createDomainInfoStoreClauses(diClauses); if (DomainAccessHandler.this.domainModel.hasChanged()) { dmClauses = domainModel.getChangeClauses(); } // now collect all clauses List<IClause> clauses = new ArrayList<IClause>(); // build return info: 0 ... OK, 1 ... ERROR (info was concurrently changed) createReturnCodeClauses(clauses, diReturn, versions); if (dmClauses != null) { clauses.addAll(dmClauses[0]); // add with clauses // the following WITH clauses are only needed when using START instead of MERGE as start clause // JcNumber retDi = new JcNumber("retDi"); // JcNumber retDm = new JcNumber("retDm"); // clauses.add(WITH.value(retDi)); // clauses.add(WITH.value(retDm)); // clauses.addAll(dmClauses[2]); } // add domain info clauses as second because they change // the stored version properties when they write to the db clauses.addAll(diClauses); // add return clauses clauses.addAll(diReturn); if (dmClauses != null) clauses.addAll(dmClauses[1]); query = new JcQuery(); query.setClauses(clauses.toArray(new IClause[clauses.size()])); context.dInfo = Exec.TRIED_STORE; if (dmClauses != null) context.dModel = Exec.TRIED_STORE; } } finally { CurrentDomain.setDomainLabel(pLab); } if (query != null) Util.printQuery(query, QueryToObserve.DOMAIN_INFO, Format.PRETTY_1); return query; } private void createReturnCodeClauses(List<IClause> clauses, List<IClause> diReturn, int[] versions) { // build return info: 0 ... OK, 1 ... ERROR (info was concurrently changed) JcNode info1 = new JcNode("info1"); clauses.addAll(createDomainInfoStartClause(info1)); JcNumber retDi = new JcNumber("retDi"); JcNumber retDm = new JcNumber("retDm"); clauses.add(WITH.collection(C.CREATE( new IClause[]{ CASE.result(), WHEN.valueOf(info1.property(DomainInfoVersionProperty)).EQUALS(versions[0]), NATIVE.cypher("0"), ELSE.perform(), NATIVE.cypher("1"), END.caseXpr().AS(retDi) }))); clauses.add(WITH.collection(C.CREATE( new IClause[]{ CASE.result(), WHEN.valueOf(info1.property(DomainInfoModelVersionProperty)).EQUALS(versions[1]), NATIVE.cypher("0"), ELSE.perform(), NATIVE.cypher("1"), END.caseXpr().AS(retDm) }))); diReturn.add(RETURN.value(retDi)); diReturn.add(RETURN.value(retDm)); } /** * @param diClauses * @return [domainInfoVersion, domainModelVersion] */ private int[] createDomainInfoStoreClauses(List<IClause> diClauses) { JcNode info = new JcNode("info"); if (DomainAccessHandler.this.domainInfo.isChanged()) { List<String> class2LabelList = DomainAccessHandler.this.domainInfo.getLabel2ClassNameStringList(); List<String> fieldComponentTypeList = DomainAccessHandler.this.domainInfo.getFieldComponentTypeStringList(); List<String> concreteFieldTypeList = DomainAccessHandler.this.domainInfo.getConcreteFieldTypeStringList(); diClauses.add(DO.SET(info.property(DomainInfoLabel2ClassProperty)).to(class2LabelList)); diClauses.add(DO.SET(info.property(DomainInfoFieldComponentTypeProperty)).to(fieldComponentTypeList)); diClauses.add(DO.SET(info.property(DomainInfoConcreteFieldTypeProperty)).to(concreteFieldTypeList)); diClauses.add(DO.SET(info.property(DomainInfoUseDomainLabelProperty)) .to(DomainAccessHandler.this.domainInfo.useDomainLabels)); } // always write version properties to domainInfo diClauses.add(DO.SET(info.property(DomainInfoVersionProperty)).to( DomainAccessHandler.this.domainInfo.getVersion())); diClauses.add(DO.SET(info.property(DomainInfoModelVersionProperty)).to( DomainAccessHandler.this.domainModel.getVersion())); // calc versions that should be found in the db if no concurrent changes were done int i_version = DomainAccessHandler.this.domainInfo.getVersion(); i_version = i_version > 0 ? i_version - 1 : i_version; int m_version = DomainAccessHandler.this.domainModel.getVersion(); m_version = m_version > 0 ? m_version - 1 : m_version; // conditionally perform modifications IClause[] clausesArray = diClauses.toArray(new IClause[diClauses.size()]); diClauses.clear(); diClauses.addAll(createDomainInfoStartClause(info)); JcValue x = new JcValue("x"); IClause clause = FOR_EACH.element(x).IN(C.CREATE(new IClause[]{ CASE.result(), WHEN.valueOf(info.property(DomainInfoVersionProperty)).EQUALS(i_version) .AND().valueOf(info.property(DomainInfoModelVersionProperty)).EQUALS(m_version), NATIVE.cypher("[1]"), ELSE.perform(), NATIVE.cypher("[]"), END.caseXpr() })).DO(clausesArray); diClauses.add(clause); return new int[] {i_version, m_version}; } /** * store if needed */ private void handleDomainInfo_Model(ExecContext context) { // make sure that still pending changes are committed to the database // can happen in certain scenarios with the first domain query executed ExecContext ctxt = context == null ? new ExecContext() : context; JcQuery query = this.createDomainInfoSyncQuery(ctxt); if (query != null) { JcQueryResult uResult = this.delegate.execute(query); List<JcError> errors = Util.collectErrors(uResult); if (errors.isEmpty()) { updateDomainInfo(uResult, ctxt); } else { throw new JcResultException(errors, "Error on update of Domain Info!"); } } } private List<IClause> createDomainInfoStartClause(JcNode info) { String pLab = CurrentDomain.label.get(); CurrentDomain.setDomainLabel(null); List<IClause> ret = new ArrayList<IClause>(); try { // ret.add(START.node(info).byId(DomainAccessHandler.this.domainInfo.nodeId)); ret.add(MERGE.node(info).label(DomainInfoNodeLabel) .property(DomainInfoNameProperty).value(DomainAccessHandler.this.domainName)); ret.add(ON_CREATE.SET(info.property(DomainInfoVersionProperty)).to(0)); ret.add(ON_CREATE.SET(info.property(DomainInfoModelVersionProperty)).to(0)); } finally { CurrentDomain.setDomainLabel(pLab); } return ret; } private JcQuery createCheckInfoVersionQuery(int[] versions) { List<IClause> clauses = new ArrayList<IClause>(); JcNode info1 = new JcNode("info1"); clauses.addAll(createDomainInfoStartClause(info1)); JcNumber retDi = new JcNumber("retDi"); JcNumber retDm = new JcNumber("retDm"); clauses.add(WITH.collection(C.CREATE( new IClause[]{ CASE.result(), WHEN.valueOf(info1.property(DomainInfoVersionProperty)).EQUALS(versions[0]), NATIVE.cypher("0"), ELSE.perform(), NATIVE.cypher("1"), END.caseXpr().AS(retDi) }))); clauses.add(WITH.collection(C.CREATE( new IClause[]{ CASE.result(), WHEN.valueOf(info1.property(DomainInfoModelVersionProperty)).EQUALS(versions[1]), NATIVE.cypher("0"), ELSE.perform(), NATIVE.cypher("1"), END.caseXpr().AS(retDm) }))); clauses.add(RETURN.value(retDi)); clauses.add(RETURN.value(retDm)); JcQuery query = new JcQuery(); query.setClauses(clauses.toArray(new IClause[clauses.size()])); return query; } /** * answer true if model and / or info was reloaded * @param result * @return */ private boolean reloadInfo_ModelIfNeeded(JcQueryResult result) { JcNumber rDi = new JcNumber("retDi"); JcNumber rDm = new JcNumber("retDm"); int retDi = ((Number)result.resultOf(rDi).get(0)).intValue(); int retDm = ((Number)result.resultOf(rDm).get(0)).intValue(); if (retDi + retDm == 0) // don't need to reload return false; else { // need to reload ExecContext ctxt = new ExecContext(); if (retDi > 0) ctxt.dInfo = Exec.RELOAD; if (retDm > 0) ctxt.dModel = Exec.RELOAD; handleDomainInfo_Model(ctxt); return true; } } } } /**********************************************/ private class ClosureCalculator { private <T> void fillModel(FillModelContext<T> context) { Step step = new Step(); step.fillModel(context, null, null, null, null); } private void calculateClosureQuery(ClosureQueryContext context) { boolean isDone = false; Step step = new Step(); int idx = -1; while (!isDone) { idx++; context.clauseRepetitionNumber = idx; isDone = step.calculateQuery(null, context); if (context.currentMatchClause != null) { context.addMatchClause(context.currentMatchClause); context.currentMatchClause = null; } } } private void calculateClosure(List<?> domainObjects, UpdateContext context) { for (Object domainObject : domainObjects) { recursiveCalculateClosure(domainObject, null, context, false); // don't delete } } /** * @param domainObject * @param context * @param prepareToDelete if true, delete outgoing relations from domainObject that later on itself will be deleted */ @SuppressWarnings("unchecked") private void recursiveCalculateClosure(Object domainObject, FieldMapping parentField, UpdateContext context, boolean prepareToDelete) { if (!context.domainObjects.contains(domainObject)) { // avoid infinite loops context.domainObjects.add(domainObject); if (domainObject instanceof iot.jcypher.domain.mapping.surrogate.Collection) { iot.jcypher.domain.mapping.surrogate.Collection surrColl = (iot.jcypher.domain.mapping.surrogate.Collection)domainObject; if (surrColl.getContent() != null) surrColl.setCollType(surrColl.getContent().getClass().getName()); } ObjectMapping objectMapping = domainAccessHandler.getObjectMappingFor(domainObject); Iterator<FieldMapping> it = objectMapping.fieldMappingsIterator(); while (it.hasNext()) { FieldMapping fm = domainAccessHandler.modifyFieldMapping(it.next(), parentField); Object obj = fm.getObjectNeedingRelation(domainObject); if (obj != null && !prepareToDelete) { // definitly need relation if (obj instanceof Collection<?>) { // collection with non-simple elements, // we won't reach this spot with empty collections Collection<?> coll = (Collection<?>)obj; handleListArrayInClosureCalc(coll, null, domainObject, context, fm); } else if (obj.getClass().isArray()) { // array with non-simple elements, // we won't reach this spot with empty arrays Object[] array = (Object[]) obj; handleListArrayInClosureCalc(null, array, domainObject, context, fm); } else if (obj instanceof Map<?, ?>) { Map<Object, Object> map = (Map<Object, Object>)obj; handleMapInClosureCalc(map, domainObject, context, fm); } else { handleObjectInClosureCalc(obj, domainObject, context, fm); } } else { obj = fm.getFieldValue(domainObject); if (obj != null) // store class field info in DomainInfo MappingUtil.internalDomainAccess.get() .addConcreteFieldType(fm.getClassFieldName(), obj.getClass()); if (fm.needsRelation()) { // in case obj == null because it was not set // no relation --> check if an old relation needs to be removed if (fm.getFieldKind() == FieldKind.COLLECTION || fm.getFieldKind() == FieldKind.MAP || fm.getFieldKind() == FieldKind.ARRAY) { // remove multiple relations if they exist List<IMapEntry> mapEntriesToRemove = handleKeyedRelationsModification(null, context, new SourceFieldKey(domainObject, fm.getFieldName()), false); removeObjectsIfNeeded(fm, context, mapEntriesToRemove); } else { // remove just a single relation if it exists IRelation relat = domainAccessHandler.getDomainState().findRelation(domainObject, fm.getPropertyOrRelationName()); if (relat != null) { context.relationsToRemove.add(relat); if (relat.getEnd() instanceof AbstractSurrogate) context.surrogateChangeLog.removed.add(relat); } } } } } } } private void handleMapInClosureCalc(Map<Object, Object> map, Object domainObject, UpdateContext context, FieldMapping fm) { MapTerminator mapTerminator = null; String typ = fm.getPropertyOrRelationName(); Map<SourceField2TargetKey, List<KeyedRelation>> keyedRelations = new HashMap<SourceField2TargetKey, List<KeyedRelation>>(); List<IMapEntry> mapEntries = new ArrayList<IMapEntry>(); List<Object> targetObjects = new ArrayList<Object>(); // store concrete type in DomainInfo String classField = fm.getClassFieldName(); MappingUtil.internalDomainAccess.get() .addConcreteFieldType(classField, map.getClass()); boolean containsMapTerm = false; DomainState ds = domainAccessHandler.getDomainState(); Iterator<Entry<Object, Object>> it = map.entrySet().iterator(); while(it.hasNext()) { Entry<Object, Object> entry = it.next(); Object val = entry.getValue(); if (val instanceof Map<?, ?>) val = ds.getSurrogateState().getCreateSurrogateFor(val, iot.jcypher.domain.mapping.surrogate.Map.class); else if (val instanceof Collection<?>) val = ds.getSurrogateState().getCreateSurrogateFor(val, iot.jcypher.domain.mapping.surrogate.Collection.class); else if (val.getClass().isArray()) val = ds.getSurrogateState().getCreateSurrogateFor(val, iot.jcypher.domain.mapping.surrogate.Array.class); Object key = entry.getKey(); if (key instanceof Collection<?>) key = ds.getSurrogateState().getCreateSurrogateFor(key, iot.jcypher.domain.mapping.surrogate.Collection.class); else if (key.getClass().isArray()) key = ds.getSurrogateState().getCreateSurrogateFor(key, iot.jcypher.domain.mapping.surrogate.Array.class); else if (key instanceof Map<?, ?>) key = ds.getSurrogateState().getCreateSurrogateFor(key, iot.jcypher.domain.mapping.surrogate.Map.class); boolean keyMapsToProperty = MappingUtil.mapsToProperty(key.getClass()); boolean valMapsToProperty = MappingUtil.mapsToProperty(val.getClass()); Object target; Object relationKey; if (keyMapsToProperty) { if (valMapsToProperty) { if (mapTerminator == null) { mapTerminator = new MapTerminator(domainObject, fm.getFieldName()); mapEntries.add(mapTerminator); containsMapTerm = true; } target = mapTerminator; } else { target = val; targetObjects.add(target); } relationKey = entry.getKey(); } else { // complex key always needs a MapEntry // handle it like a list for correct removal of removed entries MapEntry mapEntry = new MapEntry(key, val); mapEntries.add(mapEntry); target = mapEntry; relationKey = (int)0; } SourceField2TargetKey s2tKey = new SourceField2TargetKey(domainObject, fm.getFieldName(), target); List<KeyedRelation> relats = keyedRelations.get(s2tKey); if (relats == null) { relats = new ArrayList<KeyedRelation>(); keyedRelations.put(s2tKey, relats); } KeyedRelation keyedRelation = new KeyedRelation(typ, relationKey, domainObject, target); if (valMapsToProperty && keyMapsToProperty) keyedRelation.setValue(val); relats.add(keyedRelation); if (target instanceof iot.jcypher.domain.mapping.surrogate.Map) context.surrogateChangeLog.added.add(keyedRelation); // store component types in DomainInfo MappingUtil.internalDomainAccess.get() .addFieldComponentType(fm.getClassFieldName(), target.getClass()); } List<IMapEntry> mapEntriesToRemove = handleKeyedRelationsModification(keyedRelations, context, new SourceFieldKey(domainObject, fm.getFieldName()), containsMapTerm); for (IMapEntry mapEntry : mapEntries) { recursiveCalculateClosure(mapEntry, fm, context, false); // don't delete } for (Object obj : targetObjects) { recursiveCalculateClosure(obj, fm, context, false); // don't delete } removeObjectsIfNeeded(fm, context, mapEntriesToRemove); } private void handleListArrayInClosureCalc(Collection<?> coll, Object[] array, Object domainObject, UpdateContext context, FieldMapping fm) { Collection<?> toIterate; String typ = fm.getPropertyOrRelationName(); Map<SourceField2TargetKey, List<KeyedRelation>> keyedRelations = new HashMap<SourceField2TargetKey, List<KeyedRelation>>(); List<Object> targetObjects = new ArrayList<Object>(); // store concrete type in DomainInfo String classField = fm.getClassFieldName(); if (coll != null) { MappingUtil.internalDomainAccess.get() .addConcreteFieldType(classField, coll.getClass()); toIterate = coll; } else { MappingUtil.internalDomainAccess.get() .addConcreteFieldType(classField, array.getClass()); toIterate = Arrays.asList(array); } int idx = 0; Iterator<?> it = toIterate.iterator(); DomainState ds = domainAccessHandler.getDomainState(); while(it.hasNext()) { Object elem = it.next(); if (elem instanceof Collection<?>) elem = ds.getSurrogateState().getCreateSurrogateFor(elem, iot.jcypher.domain.mapping.surrogate.Collection.class); else if (elem.getClass().isArray()) elem = ds.getSurrogateState().getCreateSurrogateFor(elem, iot.jcypher.domain.mapping.surrogate.Array.class); else if (elem instanceof Map<?, ?>) elem = ds.getSurrogateState().getCreateSurrogateFor(elem, iot.jcypher.domain.mapping.surrogate.Map.class); SourceField2TargetKey key = new SourceField2TargetKey(domainObject, fm.getFieldName(), elem); targetObjects.add(elem); List<KeyedRelation> relats = keyedRelations.get(key); if (relats == null) { relats = new ArrayList<KeyedRelation>(); keyedRelations.put(key, relats); } KeyedRelation keyedRelation = new KeyedRelation(typ, idx, domainObject, elem); relats.add(keyedRelation); if (elem instanceof iot.jcypher.domain.mapping.surrogate.Collection) context.surrogateChangeLog.added.add(keyedRelation); // store component type in DomainInfo MappingUtil.internalDomainAccess.get() .addFieldComponentType(classField, elem.getClass()); idx++; } handleKeyedRelationsModification(keyedRelations, context, new SourceFieldKey(domainObject, fm.getFieldName()), false); for (Object elem : targetObjects) { recursiveCalculateClosure(elem, fm, context, false); // don't delete } } private void handleObjectInClosureCalc(Object relatedObject, Object domainObject, UpdateContext context, FieldMapping fm) { IRelation relat = new Relation(fm.getPropertyOrRelationName(), domainObject, relatedObject); DomainState ds = domainAccessHandler.getDomainState(); if (relatedObject instanceof AbstractSurrogate) context.surrogateChangeLog.added.add(relat); if (!ds.existsRelation(relat)) { context.relations.add(relat); // relation not in db // check if an old relation (for the same field but to another object, which is now replaced // by the new relation) needs to be removed. relat = ds.findRelation(domainObject, fm.getPropertyOrRelationName()); if (relat != null) { context.relationsToRemove.add(relat); } } // store concrete type in DomainInfo String classField = fm.getClassFieldName(); MappingUtil.internalDomainAccess.get() .addConcreteFieldType(classField, relatedObject.getClass()); recursiveCalculateClosure(relatedObject, fm, context, false); // don't delete } private List<IMapEntry> handleKeyedRelationsModification(Map<SourceField2TargetKey, List<KeyedRelation>> keyedRelations, UpdateContext context, SourceFieldKey fieldKey, boolean containsMapTerm) { List<KeyedRelation> allExistingRels = new ArrayList<KeyedRelation>(); DomainState ds = domainAccessHandler.getDomainState(); List<KeyedRelation> allExist = ds.getKeyedRelations(fieldKey); if (allExist != null) allExistingRels.addAll(allExist); if (keyedRelations != null) { Iterator<Entry<SourceField2TargetKey, List<KeyedRelation>>> it = keyedRelations.entrySet().iterator(); while(it.hasNext()) { Entry<SourceField2TargetKey, List<KeyedRelation>> entry = it.next(); List<KeyedRelation> existingRels = ds.getKeyedRelations(entry.getKey()); RelationsToModify toModify = calculateKeyedRelationsToModify(entry.getValue(), existingRels, allExistingRels); context.relations.addAll(toModify.toChange); context.relations.addAll(toModify.toCreate); context.relationsToRemove.addAll(toModify.toRemove); } } // in allExistingRels we have those which previously existed but don't exist in the collection or map any more context.relationsToRemove.addAll(allExistingRels); List<IMapEntry> mapEntriesToRemove = new ArrayList<IMapEntry>(); boolean mapTermAdded = false; for(KeyedRelation kRel : allExistingRels) { // they are to be removed Object end = kRel.getEnd(); if (end instanceof MapEntry) { // remove MapEntry // TODO is the check really needed if (!mapEntriesToRemove.contains(end)) mapEntriesToRemove.add((MapEntry) end); } else if (end instanceof MapTerminator) { if (!containsMapTerm && !mapTermAdded) { mapTermAdded = true; mapEntriesToRemove.add((MapTerminator)end); } } else if (end instanceof AbstractSurrogate) { context.surrogateChangeLog.removed.add(kRel); } } return mapEntriesToRemove; } private RelationsToModify calculateKeyedRelationsToModify(List<KeyedRelation> actual, List<KeyedRelation> existingInGraph, List<KeyedRelation> allExistingInGraph) { List<KeyedRelation> act = new ArrayList<KeyedRelation>(); act.addAll(actual); List<KeyedRelation> existingOnes = new ArrayList<KeyedRelation>(); if (existingInGraph != null) existingOnes.addAll(existingInGraph); List<KeyedRelation> unchanged = new ArrayList<KeyedRelation>(); for (KeyedRelation exists : existingOnes) { for (KeyedRelation iRel : act) { if (exists.equals(iRel)) { unchanged.add(iRel); break; } } } for (KeyedRelation iRel : unchanged) { act.remove(iRel); existingOnes.remove(iRel); allExistingInGraph.remove(iRel); } // now we have filtered out those which do not need to be changed, added or removed int maxRemoveIndex = -1; List<KeyedRelationToChange> toChange = new ArrayList<KeyedRelationToChange>(); int idx = 0; for (KeyedRelation iRel : act) { if (existingOnes.size() > idx) { toChange.add(new KeyedRelationToChange(existingOnes.get(idx), iRel)); maxRemoveIndex = idx; } else break; idx++; } if (maxRemoveIndex != -1) { for (int i = maxRemoveIndex; i >= 0; i--) { KeyedRelation removed = existingOnes.remove(i); act.remove(i); allExistingInGraph.remove(removed); } } // now we have filtered out those which need to be changed (they will get a new key) // they are in list 'toChange'. RelationsToModify ret = new RelationsToModify(); ret.toChange = toChange; // the actual ones which exist in the graph and the actual ones which can be created // by changing existing ones, have been removed from act. // So in 'act' there are those who need to be created, // We now find them in list 'toCreate' ret.toCreate = act; // Those which really need to be removed are now found in list 'toRemove' ret.toRemove = existingOnes; // existing ones are already added to toRemove, avoid doing it again later allExistingInGraph.removeAll(existingOnes); return ret; } private void removeObjectsIfNeeded(FieldMapping parentField, UpdateContext context, List<IMapEntry> mapEntriesToRemove) { if (mapEntriesToRemove.size() > 0) { for(Object obj : mapEntriesToRemove) { // remove MapEntry objects recursiveCalculateClosure(obj, parentField, context, true); // prepare for remove context.domainObjectsToRemove.add(obj); } } } /**********************************************/ private class RelationsToModify { private List<KeyedRelation> toCreate; private List<KeyedRelation> toRemove; private List<KeyedRelationToChange> toChange; } /**********************************************/ private class Step { private int subPathIndex = -1; private Step next; /** * @param context * @param fm may be null * @param nodeName may be null */ @SuppressWarnings("unchecked") private <T> void fillModel(FillModelContext<T> context, FieldMapping fm, String nodeName, String relationName, GrNode parentNode) { DomainState ds = domainAccessHandler.getDomainState(); boolean resetInnerClassResolution = false; int prevClauseRepetitionNumber = context.clauseRepetitionNumber; boolean isRoot = fm == null; String nnm; if (nodeName != null) nnm = nodeName; else nnm = this.buildNodeOrRelationName(context.path, DomainAccessHandler.NodePrefix, context.clauseRepetitionNumber); context.setTerminatesClause(nnm); CompoundObjectType compoundType; if (isRoot) // root type compoundType = domainAccessHandler.getCompoundTypeFor(context.domainObjectClass); else { String classFieldName = fm.getClassFieldName(); compoundType = MappingUtil.internalDomainAccess.get() .getConcreteFieldType(classFieldName); } Class<? extends Object> pureType = fm != null ? fm.getFieldType() : context.domainObjectClass; if (compoundType != null) { // else this field cannot have been stored in the graph earlier // prepare for navigation to next node context.path.add(new PathElement(pureType)); boolean resolveDeep = true; boolean maxDepthReached = context.maxResolutionDepth >= 0 && context.maxResolutionDepth == context.currentDepth; if (maxDepthReached) resolveDeep = false; else if (context.recursionExitNodes.contains(nnm)) // exit recursion resolveDeep = false; FieldKind fieldKind = fm != null ? fm.getFieldKind() : FieldKind.SINGLE; SurrogateContent surrogateContent = checkForSurrogates(context, fm, fieldKind, compoundType); Collection<Object> collection = surrogateContent.collection; Map<Object, Object> map = surrogateContent.map; List<Object> array = surrogateContent.array; if (surrogateContent.compoundType != null) compoundType = surrogateContent.compoundType; // initialize for loop iteration Set<GrRelation> relationList; // make list of relations distinct Iterator<GrRelation> relationsIterator = null; List<GrRelation> usedRelations = null; GrNode actNode = null; GrRelation actRelation = null; boolean loopDone = true; if (isRoot) { JcNode n = new JcNode(nnm); List<GrNode> nodeList = context.qResult.resultOf(n); if (nodeList.size() > 0) { actNode = nodeList.get(0); // there can only be one loopDone = false; } } else { JcRelation r = new JcRelation(relationName); relationList = new LinkedHashSet<GrRelation>(context.qResult.resultOf(r)); relationsIterator = relationList.iterator(); if (relationsIterator.hasNext()) { actRelation = relationsIterator.next(); loopDone = false; } usedRelations = new ArrayList<GrRelation>(); } Object domainObject = null; if (!loopDone) { // at least one result node exists for this pattern int initialMaxClauseRepetitionNumber = context.maxClauseRepetitionNumber; while (!loopDone) { if (!isRoot) { actNode = null; if (actRelation != null) { if (actRelation.getStartNode().getId() != parentNode.getId()) { if (relationsIterator.hasNext()) actRelation = relationsIterator.next(); else loopDone = true; continue; } actNode = actRelation.getEndNode(); usedRelations.add(actRelation); } } // else is root if (actNode != null) { // null values are supported boolean performMapping = false; boolean mapProperties = true; // check if a domain object has already been mapped to this node domainObject = ds.getFrom_Id2ObjectMap(actNode.getId()); if (domainObject == null) { Class<?> clazz = domainAccessHandler.findClassToInstantiateFor(actNode); if (clazz.equals(MapTerminator.class)) domainObject = new MapTerminator(context.parentObject, fm.getFieldName()); else domainObject = domainAccessHandler.createInstance(clazz); if (domainObject instanceof Array) ((Array)domainObject).setSurrogateState(ds.getSurrogateState()); GrProperty prop = actNode.getProperty(ResultHandler.lockVersionProperty); int v = -1; if (prop != null) v = ((Number)prop.getValue()).intValue(); ds.add_Id2Object(domainObject, actNode.getId(), v, resolveDeep ? ResolutionDepth.DEEP : ResolutionDepth.SHALLOW); if (domainObject instanceof InnerClassSurrogate) { ((InnerClassSurrogate)domainObject).setId2ObjectMapper(getInternalDomainAccess()); ((InnerClassSurrogate)domainObject).setNodeId(actNode.getId()); } performMapping = true; // recursion exit if (!resolveDeep && !maxDepthReached) { if (!(domainObject instanceof InnerClassSurrogate)) context.addRecursionExitObject(domainObject, context.currentDepth); else { if (!context.resolveInnerClasses) { ((InnerClassSurrogate)domainObject).setRecursionExit(context); ((InnerClassSurrogate)domainObject).setActResolutionDepth(context.currentDepth); context.resolveInnerClasses = true; resetInnerClassResolution = true; } } } } else { // domainObject has at least been shallowly mapped boolean forceMapProperties = this.handleReResolve(domainObject, ds); if (ds.getResolutionDepth(domainObject) != ResolutionDepth.DEEP) { boolean removed = false; if (maxDepthReached) { context.removeRecursionExitObject(domainObject); removed = true; } if (resolveDeep) { performMapping = true; mapProperties = forceMapProperties; // properties have already been mapped // but may be forced to be re-mapped ds.getLoadInfoFrom_Object2IdMap(domainObject) .setResolutionDepth(ResolutionDepth.DEEP); if (!removed) context.removeRecursionExitObject(domainObject); } else { // recursion exit if (!maxDepthReached) { if (!(domainObject instanceof InnerClassSurrogate)) context.addRecursionExitObject(domainObject, context.currentDepth); else { if (!context.resolveInnerClasses) { ((InnerClassSurrogate)domainObject).setRecursionExit(context); ((InnerClassSurrogate)domainObject).setActResolutionDepth(context.currentDepth); context.resolveInnerClasses = true; resetInnerClassResolution = true; } } } } } } if (fm == null) { // we are at the root level context.domainObject = (T) domainObject; } if (performMapping) { // need to reset if we iterate through a list context.maxClauseRepetitionNumber = initialMaxClauseRepetitionNumber; ObjectMapping objectMapping = domainAccessHandler .getCompoundObjectMappingFor(compoundType, domainObject); Iterator<FieldMapping> it = objectMapping.fieldMappingsIterator(); boolean hasComplexFields = false; int idx = 0; while (it.hasNext()) { FieldMapping fMap = domainAccessHandler.modifyFieldMapping(it.next(), fm); idx++; // index starts with 1 so as not to mix with the root node (n_0) if (!objectMapping.shouldPerformFieldMapping(fMap)) { if (fMap.needsRelation() && resolveDeep) { calculateMaxClauseRepetitionNumber(context, fMap, idx); } continue; } boolean mapped = false; if (fMap.needsRelationOrProperty()) { mapped = fMap.mapPropertyToField(domainObject, actNode); } if (fMap.needsRelation()) { hasComplexFields = true; if (resolveDeep || fMap.isInnerClassRefField()) { if (!mapped) { PathElement pe = context.getLastPathElement(); pe.fieldIndex = idx; context.clauseRepetitionNumber = context.maxClauseRepetitionNumber; pe.fieldName = fMap.getFieldName(); pe.propOrRelName = fMap.getPropertyOrRelationName(); pe.sourceType = fMap.getField().getDeclaringClass(); String ndName = this.buildNodeOrRelationName(context.path, DomainAccessHandler.NodePrefix, context.clauseRepetitionNumber); boolean needToRepeat = false; if (isValidNodeName(ndName, context)) { Object prevParent = context.parentObject; context.parentObject = domainObject; String rnm = this.buildNodeOrRelationName(context.path, DomainAccessHandler.RelationPrefix, context.clauseRepetitionNumber); context.currentDepth++; this.fillModel(context, fMap, ndName, rnm, actNode); context.currentDepth--; context.parentObject = prevParent; } else needToRepeat = true; // need to repeat context.alreadyTested.clear(); while(needToRepeat && morePathsToTest(context, fMap, idx)) { pe = context.getLastPathElement(); pe.fieldIndex = idx; context.clauseRepetitionNumber = context.maxClauseRepetitionNumber; pe.fieldName = fMap.getFieldName(); pe.propOrRelName = fMap.getPropertyOrRelationName(); pe.sourceType = fMap.getField().getDeclaringClass(); ndName = this.buildNodeOrRelationName(context.path, DomainAccessHandler.NodePrefix, context.clauseRepetitionNumber); needToRepeat = false; if (isValidNodeName(ndName, context)) { Object prevParent = context.parentObject; context.parentObject = domainObject; String rnm = this.buildNodeOrRelationName(context.path, DomainAccessHandler.RelationPrefix, context.clauseRepetitionNumber); context.currentDepth++; this.fillModel(context, fMap, ndName, rnm, actNode); context.currentDepth--; context.parentObject = prevParent; } else { if (moreClausesAvailable(ndName, context)) needToRepeat = true; // need to repeat } } context.updateMaxClauseRepetitionNumber(); } } } else { if (mapProperties && !mapped) fMap.mapPropertyToField(domainObject, actNode); } } boolean removed = false; if (!hasComplexFields && !resolveDeep) { context.removeRecursionExitObject(domainObject); removed = true; // domainObject needs no further resolution ds.getLoadInfoFrom_Object2IdMap(domainObject) .setResolutionDepth(ResolutionDepth.DEEP); } if (maxDepthReached && !removed) context.removeRecursionExitObject(domainObject); } } if (relationsIterator == null) loopDone = true; else { loopDone = !relationsIterator.hasNext(); if (!loopDone) actRelation = relationsIterator.next(); } } // end of loop if (!isRoot) { if (fieldKind == FieldKind.COLLECTION) { addCollectionRelations(context, usedRelations, collection, null, fm.getPropertyOrRelationName()); if (collection.isEmpty()) { // test for empty collection, which eventually was mapped to a property fm.mapPropertyToField(context.parentObject, parentNode); domainObject = null; } else domainObject = collection; } else if (fieldKind == FieldKind.ARRAY) { if (context.parentObject instanceof Array) ((Array)context.parentObject).setSize(usedRelations.size()); addCollectionRelations(context, usedRelations, null, array, fm.getPropertyOrRelationName()); if (array.isEmpty()) { // test for empty array, which eventually was mapped to a property fm.mapPropertyToField(context.parentObject, parentNode); domainObject = null; } else domainObject = array; } else if (fieldKind == FieldKind.MAP) { addMapRelations_FillMap(context, usedRelations, fm.getPropertyOrRelationName(), map); if (map.isEmpty()) { // test for empty map, which evantually was mapped to a property fm.mapPropertyToField(context.parentObject, parentNode); } domainObject = null; } else if (usedRelations.size() > 0) { GrRelation rel = usedRelations.get(0); Relation relat = new Relation(fm.getPropertyOrRelationName(), context.parentObject, domainObject); GrProperty prop = rel.getProperty(ResultHandler.lockVersionProperty); int v = -1; if (prop != null) v = ((Number)prop.getValue()).intValue(); ds.add_Id2Relation( relat, rel.getId(), v); if (domainObject instanceof AbstractSurrogate) context.surrogateChangeLog.added.add(relat); } if (domainObject instanceof AbstractSurrogate) { IDeferred deferred; if (context.parentObject instanceof MapEntry) { deferred = new Surrogate2MapEntry( fm.getFieldName(), (MapEntry) context.parentObject, (AbstractSurrogate)domainObject); } else { // TODO are there more possibilities ? maps in lists and vice versa ? // no, because there is no equivalent to MapEntry stored in the graph for lists deferred = new Deferred2DO(fm, (AbstractSurrogate)domainObject, context.parentObject); } context.deferredList.add(deferred); domainObject = null; } if (domainObject != null) { fm.setFieldValue(context.parentObject, domainObject); } } } context.path.remove(context.path.size() - 1); // remove the last one } context.clauseRepetitionNumber = prevClauseRepetitionNumber; if (resetInnerClassResolution) context.resolveInnerClasses = false; } private boolean handleReResolve(Object domainObject, DomainState ds) { boolean ret = false; ReResolve rer = DomainAccess.this.domainAccessHandler.reResolve.get(); if (rer != null) { if (!rer.reResolved.contains(domainObject)) { rer.reResolved.add(domainObject); LoadInfo li = ds.getLoadInfoFrom_Object2IdMap(domainObject); li.setResolutionDepth(ResolutionDepth.SHALLOW); ret = true; } } return ret; } @SuppressWarnings("unchecked") private <T> SurrogateContent checkForSurrogates(FillModelContext<T> context, FieldMapping fm, FieldKind fieldKind, CompoundObjectType compoundType) { SurrogateContent ret = new SurrogateContent(); DomainState ds = domainAccessHandler.getDomainState(); if (fieldKind == FieldKind.COLLECTION) { if (context.parentObject instanceof iot.jcypher.domain.mapping.surrogate.Collection) { ret.collection = ((iot.jcypher.domain.mapping.surrogate.Collection)context.parentObject).getContent(); } String classFieldName = fm.getClassFieldName(); ret.compoundType = MappingUtil.internalDomainAccess.get() .getFieldComponentType(classFieldName); if (ret.collection == null) { // select the first concrete type in the CompoundType to instantiate. // Most certainly there will only be one type in the CompoundType, // anyway it must be instantiable as it has earlier been stored to the graph ret.collection = (Collection<Object>) domainAccessHandler.createInstance(MappingUtil.internalDomainAccess.get() .getConcreteFieldType(classFieldName).getType()); if (context.parentObject instanceof iot.jcypher.domain.mapping.surrogate.Collection) { ((iot.jcypher.domain.mapping.surrogate.Collection)context.parentObject) .setContent(ret.collection); ds.getSurrogateState() .addOriginal2Surrogate(ret.collection, (iot.jcypher.domain.mapping.surrogate.Collection)context.parentObject); } } } else if (fieldKind == FieldKind.ARRAY) { if (context.parentObject instanceof iot.jcypher.domain.mapping.surrogate.Array) { ret.array = ((iot.jcypher.domain.mapping.surrogate.Array)context.parentObject).getListContent(); } String classFieldName = fm.getClassFieldName(); ret.compoundType = MappingUtil.internalDomainAccess.get() .getFieldComponentType(classFieldName); if (ret.array == null) { ret.array = new ObservableList<Object>(); if (context.parentObject instanceof iot.jcypher.domain.mapping.surrogate.Array) { ((iot.jcypher.domain.mapping.surrogate.Array)context.parentObject) .setListContent(ret.array); } } } else if (fieldKind == FieldKind.MAP) { if (context.parentObject instanceof iot.jcypher.domain.mapping.surrogate.Map) { ret.map = ((iot.jcypher.domain.mapping.surrogate.Map)context.parentObject).getContent(); } String classFieldName = fm.getClassFieldName(); ret.compoundType = MappingUtil.internalDomainAccess.get() .getFieldComponentType(classFieldName); if (ret.map == null) { // select the first concrete type in the CompoundType to instantiate. // Most certainly there will only be one type in the CompoundType, // anyway it must be instantiable as it has earlier been stored to the graph ret.map = (Map<Object, Object>) domainAccessHandler.createInstance(MappingUtil.internalDomainAccess.get() .getConcreteFieldType(classFieldName).getType()); if (context.parentObject instanceof iot.jcypher.domain.mapping.surrogate.Map) { ((iot.jcypher.domain.mapping.surrogate.Map)context.parentObject) .setContent(ret.map); ds.getSurrogateState() .addOriginal2Surrogate(ret.map, (iot.jcypher.domain.mapping.surrogate.Map)context.parentObject); } } } return ret; } @SuppressWarnings({ "unchecked", "rawtypes" }) private <T> void addCollectionRelations(FillModelContext<T> context, List<GrRelation> relList, Collection coll, List<Object> array, String relType) { Iterator<GrRelation> rit = relList.iterator(); List<KeyedRelation> toResort = new ArrayList<KeyedRelation>(); ListEntriesUpdater listUpdater = null; long prevIndex = -1; boolean needResort = false; DomainState ds = null; while(rit.hasNext()) { if (ds == null) ds = domainAccessHandler.getDomainState(); GrRelation rel = rit.next(); Object domainObject = ds.getFrom_Id2ObjectMap( rel.getEndNode().getId()); GrProperty prop = rel.getProperty(DomainAccessHandler.KeyProperty); int idx = (Integer)MappingUtil.convertFromProperty(prop.getValue(), Integer.class); if (idx <= prevIndex) needResort = true; prevIndex = idx; KeyedRelation irel = new KeyedRelation(relType, idx, context.parentObject, domainObject); prop = rel.getProperty(ResultHandler.lockVersionProperty); int v = -1; if (prop != null) v = ((Number)prop.getValue()).intValue(); ds.add_Id2Relation(irel, rel.getId(), v); boolean fillList = true; if (domainObject instanceof iot.jcypher.domain.mapping.surrogate.AbstractSurrogate) { if (listUpdater == null) { listUpdater = new ListEntriesUpdater(coll != null ? coll : array); context.deferredList.add(listUpdater); } IDeferred deferred = new Surrogate2ListEntry(idx, listUpdater, (iot.jcypher.domain.mapping.surrogate.AbstractSurrogate)domainObject); context.deferredList.add(deferred); fillList = false; } if (fillList) { toResort.add(irel); } } if (needResort) { Collections.sort(toResort, new Comparator<KeyedRelation>() { @Override public int compare(KeyedRelation o1, KeyedRelation o2) { return Integer.compare(((Integer)o1.getKey()).intValue(), ((Integer)o2.getKey()).intValue()); } }); } for (KeyedRelation irel : toResort) { if (coll != null) { if (!coll.contains(irel.getEnd())) coll.add(irel.getEnd()); } else { if (!array.contains(irel.getEnd())) array.add(irel.getEnd()); } } } private <T> void addMapRelations_FillMap(FillModelContext<T> context, List<GrRelation> relList, String relType, Map<Object, Object> map) { try { Object start = context.parentObject; Map<Long, Boolean> handledRelations = new HashMap<Long, Boolean>(); Iterator<GrRelation> rit = relList.iterator(); DomainState ds = null; while(rit.hasNext()) { if (ds == null) ds = domainAccessHandler.getDomainState(); GrRelation rel = rit.next(); long relId = rel.getId(); if (handledRelations.get(relId) == null) { handledRelations.put(relId, Boolean.TRUE); Object end = ds.getFrom_Id2ObjectMap( rel.getEndNode().getId()); GrProperty prop = rel.getProperty(DomainAccessHandler.KeyProperty); GrProperty typeProp = rel.getProperty(DomainAccessHandler.KeyTypeProperty); Object key = MappingUtil.convertFromProperty(prop.getValue(), domainAccessHandler.domainModel.getClassForName(typeProp.getValue().toString())); KeyedRelation irel = new KeyedRelation(relType, key, start, end); Object val = null; prop = rel.getProperty(DomainAccessHandler.ValueProperty); if (prop != null) { typeProp = rel.getProperty(DomainAccessHandler.ValueTypeProperty); val = MappingUtil.convertFromProperty(prop.getValue(), domainAccessHandler.domainModel.getClassForName(typeProp.getValue().toString())); irel.setValue(val); } prop = rel.getProperty(ResultHandler.lockVersionProperty); int v = -1; if (prop != null) v = ((Number)prop.getValue()).intValue(); ds.add_Id2Relation(irel, relId, v); boolean fillMap = true; if (end instanceof MapEntry) { // store for later update MapEntryUpdater deferred = new MapEntryUpdater((MapEntry)end, map); context.deferredList.add(deferred); fillMap = false; } else if (!(end instanceof MapTerminator)) { val = end; if (val instanceof iot.jcypher.domain.mapping.surrogate.AbstractSurrogate) { // key instanceof iot.jcypher.domain.mapping.surrogate.Map can not happen MapEntry me = new MapEntry(key, null); IDeferred deferred = new MapEntryUpdater(me, map); context.deferredList.add(deferred); deferred = new Surrogate2MapEntry( Surrogate2MapEntry.valueField, me, (iot.jcypher.domain.mapping.surrogate.AbstractSurrogate)val); context.deferredList.add(deferred); fillMap = false; } } if (fillMap) map.put(key, val); } } } catch(Throwable e) { throw new RuntimeException(e); } } private <T> boolean isValidNodeName(String nodeName, FillModelContext<T> context) { for (String endNode : context.queryEndNodes) { if (endNode.indexOf(nodeName) == 0) return true; } return false; } private <T> boolean moreClausesAvailable(String nodeName, FillModelContext<T> context) { int idx1 = nodeName.indexOf('_') + 1; int idx2 = nodeName.indexOf('_', idx1); String clauseNum = nodeName.substring(idx1, idx2); return Integer.parseInt(clauseNum) < context.queryEndNodes.size() - 1; } private <T> void calculateMaxClauseRepetitionNumber(FillModelContext<T> context, FieldMapping fMap, int fieldIndex) { String toCompare = pathToTest(context, fMap, fieldIndex); int increment = 0; for (String nodeName : context.queryEndNodes) { if (!context.alreadyTested.contains(nodeName)) { int idx = nodeName.indexOf('_', nodeName.indexOf('_') + 1); String other = nodeName.substring(idx + 1); if (other.indexOf(toCompare) == 0) { // is a node in the sub path increment++; } } } context.maxClauseRepetitionNumber = context.maxClauseRepetitionNumber + increment; return; } private <T> boolean morePathsToTest(FillModelContext<T> context, FieldMapping fMap, int fieldIndex) { String toCompare = pathToTest(context, fMap, fieldIndex); boolean goOn = false; for (String nodeName : context.queryEndNodes) { // step to where the last test ended if (!goOn && !context.alreadyTested.contains(nodeName)) { goOn = true; } if (goOn) { int idx = nodeName.indexOf('_', nodeName.indexOf('_') + 1); String other = nodeName.substring(idx + 1); if (other.indexOf(toCompare) == 0) { // is a node in the sub path context.alreadyTested.add(nodeName); context.maxClauseRepetitionNumber++; return true; } } } return false; } private <T> String pathToTest(FillModelContext<T> context, FieldMapping fMap, int fieldIndex) { PathElement pe = context.getLastPathElement(); pe.fieldIndex = fieldIndex; context.clauseRepetitionNumber = context.maxClauseRepetitionNumber; pe.fieldName = fMap.getFieldName(); pe.propOrRelName = fMap.getPropertyOrRelationName(); pe.sourceType = fMap.getField().getDeclaringClass(); String nnm = this.buildNodeOrRelationName(context.path, DomainAccessHandler.NodePrefix, context.clauseRepetitionNumber); int idx = nnm.indexOf('_', nnm.indexOf('_') + 1); return nnm.substring(idx + 1); } /** * @param fm, null for the root step * @param context * @return true, if calculating query for the current path is done */ private boolean calculateQuery(FieldMapping fm, ClosureQueryContext context) { boolean ret = true; CompoundObjectType compoundType; if (fm == null) { // root type Class<?> domClass = context.domainObjectClass; compoundType = domainAccessHandler.getCompoundTypeFor(domClass); } else { String classFieldName = fm.getClassFieldName(); compoundType = MappingUtil.internalDomainAccess.get() .getConcreteFieldType(classFieldName); } Class<? extends Object> pureType = fm != null ? fm.getFieldType() : context.domainObjectClass; if (fm != null) // don't add a match for the start node itself this.addToQuery(fm, context); // navigate to this node boolean resolveDeep = true; boolean walkedToIndex = this.subPathIndex == -1; // visiting the first time boolean subPathWalked = false; // do the following check to avoid infinite loops if (walkedToIndex) { // we are visiting the first time if (context.getPathSize() >= domainAccessHandler.maxPathSize) resolveDeep = false; // to make sure, that the node itself is added as an end node to the query if (fm != null) subPathWalked = true; } // prepare for navigation to next node context.path.add(new PathElement(pureType)); boolean isCollection = Collection.class.isAssignableFrom(pureType); boolean isMap = Map.class.isAssignableFrom(pureType); if (isCollection || isMap) { compoundType = MappingUtil.internalDomainAccess.get() .getFieldComponentType(fm.getClassFieldName()); } boolean terminatesClause = true; if (compoundType != null) { // else no instance of that class has yet been stored in the database ObjectMapping objectMapping = domainAccessHandler.getCompoundObjectMappingFor(compoundType, null); Iterator<FieldMapping> it = objectMapping.fieldMappingsIterator(); int idx = 0; while (it.hasNext()) { FieldMapping fMap = domainAccessHandler.modifyFieldMapping(it.next(), fm); idx++; // index starts with 1 so as not to mix with the root node (n_0) if (!walkedToIndex) { if (idx != this.subPathIndex) // until subPathIndex is reached continue; else walkedToIndex = true; } if (fMap.needsRelation() && (resolveDeep || fMap.isInnerClassRefField())) { boolean needToComeBack = false; if (!subPathWalked) { terminatesClause = false; if (this.next == null) this.next = new Step(); PathElement pe = context.getLastPathElement(); pe.fieldIndex = idx; pe.fieldName = fMap.getFieldName(); pe.propOrRelName = fMap.getPropertyOrRelationName(); pe.sourceType = fMap.getField().getDeclaringClass(); boolean isDone = this.next.calculateQuery(fMap, context); if (!isDone) { // sub path not finished needToComeBack = true; } else { this.next = null; subPathWalked = true; } } else { needToComeBack = true; } if (needToComeBack) { this.subPathIndex = idx; ret = false; break; } } } } context.path.remove(context.path.size() - 1); // remove the last one if (!resolveDeep || terminatesClause) { // clause ends here String nm = this.buildNodeOrRelationName(context.path, DomainAccessHandler.NodePrefix, context.clauseRepetitionNumber); context.queryEndNodes.add(nm); if (!resolveDeep) // exits a recursion context.recursionExitNodes.add(nm); } return ret; } private void addToQuery(FieldMapping fm, ClosureQueryContext context) { if (context.currentMatchClause == null) { JcNode n = new JcNode(DomainAccessHandler.NodePrefix.concat(String.valueOf(0))); context.currentMatchClause = OPTIONAL_MATCH.node(n); if (context.matchClauses != null && context.matchClauses.size() > 0) { context.matchClauses.add(SEPARATE.nextClause()); } } JcNode n = new JcNode(this.buildNodeOrRelationName(context.path, DomainAccessHandler.NodePrefix, context.clauseRepetitionNumber)); JcRelation r = new JcRelation(this.buildNodeOrRelationName(context.path, DomainAccessHandler.RelationPrefix, context.clauseRepetitionNumber)); context.currentMatchClause.relation(r).out().type(fm.getPropertyOrRelationName()) .node(n); } private String buildNodeOrRelationName(List<PathElement> path, String prefix, int clauseNumber) { // format of node name: n_clauseNumber_idx1_idx2_idx3... StringBuilder sb = new StringBuilder(); sb.append(prefix); if (path.size() > 0) { sb.append(clauseNumber); sb.append('_'); for (int i = 0; i < path.size(); i++) { if (i > 0) sb.append('_'); sb.append(path.get(i).fieldIndex); } } else sb.append(0); // root node name return sb.toString(); } /***********************************/ private class SurrogateContent { private Collection<Object> collection; private Map<Object, Object> map; private List<Object> array; private CompoundObjectType compoundType; } } } /***********************************/ private class FillModelContext<T> implements IRecursionExit { private JcQueryResult qResult; private Class<T> domainObjectClass; private T domainObject; private Object parentObject; private List<PathElement> path; private List<String> queryEndNodes; private List<String> recursionExitNodes; private int clauseRepetitionNumber; private int maxClauseRepetitionNumber; private boolean terminatesClause; private List<String> alreadyTested; private List<ResolvedDepth> recursionExitObjects; private List<IDeferred> deferredList; private SurrogateChangeLog surrogateChangeLog; private int maxResolutionDepth; private int currentDepth; private boolean resolveInnerClasses; private FillModelContext(Class<T> domainObjectClass, JcQueryResult qResult, List<String> queryEndNds, List<String> recursionExitNds, SurrogateChangeLog surrogateChangeLog, int maxResolutionDepth, int startDepth) { super(); this.domainObjectClass = domainObjectClass; this.qResult = qResult; this.path = new ArrayList<PathElement>(); this.queryEndNodes = queryEndNds; this.recursionExitNodes = recursionExitNds; this.clauseRepetitionNumber = 0; this.maxClauseRepetitionNumber = 0; this.terminatesClause = false; this.alreadyTested = new ArrayList<String>(); this.recursionExitObjects = new ArrayList<ResolvedDepth>(); this.deferredList = new ArrayList<IDeferred>(); this.surrogateChangeLog = surrogateChangeLog; this.maxResolutionDepth = maxResolutionDepth; this.currentDepth = startDepth; this.resolveInnerClasses = false; } private PathElement getLastPathElement() { if (this.path.size() > 0) return this.path.get(this.path.size() - 1); return null; } private void setTerminatesClause(String nodeName) { this.terminatesClause = this.queryEndNodes.contains(nodeName); } private void updateMaxClauseRepetitionNumber() { if (this.terminatesClause) { this.maxClauseRepetitionNumber++; this.terminatesClause = false; } } @Override public void addRecursionExitObject(Object obj, int resolvedDepth) { if (!this.contains(this.recursionExitObjects, obj)) this.recursionExitObjects.add(new ResolvedDepth(obj, resolvedDepth)); } private void removeRecursionExitObject(Object obj) { int idx = -1; for (int i = 0; i < this.recursionExitObjects.size(); i++) { if (this.recursionExitObjects.get(i).domainObject.equals(obj)) { idx = i; break; } } if (idx != -1) this.recursionExitObjects.remove(idx); } private boolean contains(List<ResolvedDepth> list, Object obj) { for (ResolvedDepth rd : list) { if (rd.domainObject.equals(obj)) return true; } return false; } /*********************************/ private class ResolvedDepth { private Object domainObject; private int resolvedDepth; private ResolvedDepth(Object domainObject, int resolvedDepth) { super(); this.domainObject = domainObject; this.resolvedDepth = resolvedDepth; } } } /***********************************/ private class ClosureQueryContext { private Class<?> domainObjectClass; private List<IClause> matchClauses; private Node currentMatchClause; private List<PathElement> path; private List<String> queryEndNodes; private List<String> recursionExitNodes; private int clauseRepetitionNumber; private ClosureQueryContext(Class<?> domainObjectClass) { super(); this.domainObjectClass = domainObjectClass; this.path = new ArrayList<PathElement>(); this.queryEndNodes = new ArrayList<String>(); this.recursionExitNodes = new ArrayList<String>(); } private void addMatchClause(IClause clause) { if (this.matchClauses == null) this.matchClauses = new ArrayList<IClause>(); this.matchClauses.add(clause); } private PathElement getLastPathElement() { if (this.path.size() > 0) return this.path.get(this.path.size() - 1); return null; } private int getRecursionCount() { int count = 0; int sz = this.path.size(); if (sz > 0) { PathElement peComp = this.path.get(sz - 1); for (int i = sz - 2; i >= 0; i--) { PathElement pe = this.path.get(i); if (pe.sourceType.equals(peComp.sourceType) && pe.propOrRelName.equals(peComp.propOrRelName)) count++; } } return count; } private int getPathSize() { return this.path.size(); } } /*********************************/ private static class PathElement { private Class<?> sourceType; private String fieldName; private int fieldIndex; private String propOrRelName; private PathElement(Class<?> sourceType) { super(); this.sourceType = sourceType; } } /***********************************/ private class UpdateContext { private List<Object> domainObjects = new ArrayList<Object>(); private List<IRelation> relations = new ArrayList<IRelation>(); private List<IRelation> relationsToRemove = new ArrayList<IRelation>(); private List<Object> domainObjectsToRemove = new ArrayList<Object>(); private Map<Object, GrNode> domObj2Node; private List<DomRelation2ResultRelation> domRelation2Relations; private Map<Integer, QueryNode2ResultNode> nodeIndexMap; private Map<Integer, QueryRelation2ResultRelation> relationIndexMap; private boolean lockingErrors; private Graph graph; private SurrogateChangeLog surrogateChangeLog = new SurrogateChangeLog(); } /***********************************/ private class DomRelation2ResultRelation { private IRelation domRelation; private GrRelation resultRelation; } /***********************************/ private class QueryNode2ResultNode { private JcNode queryNode; private GrNode resultNode; private int version; } /***********************************/ private class QueryRelation2ResultRelation { private JcRelation queryRelation; private GrRelation resultRelation; private int version; } /***********************************/ private class DomainInfo { private boolean changed; private long nodeId; private Map<String, Class<?>> label2ClassMap; private Map<Class<?>, String> class2labelMap; private Map<String, CompoundObjectType> fieldComponentTypeMap; private Map<String, CompoundObjectType> concreteFieldTypeMap; private Map<Class<?>, List<BackwardField>> componentTypeBackward; private Map<Class<?>, List<BackwardField>> fieldTypeBackward; private boolean useDomainLabels; private int version; private DomainInfo(long nid) { super(); this.changed = false; this.nodeId = nid; this.label2ClassMap = new HashMap<String, Class<?>>(); this.class2labelMap = new HashMap<Class<?>, String>(); this.fieldComponentTypeMap = new HashMap<String, CompoundObjectType>(); this.concreteFieldTypeMap = new HashMap<String, CompoundObjectType>(); this.componentTypeBackward = new HashMap<Class<?>, List<BackwardField>>(); this.fieldTypeBackward = new HashMap<Class<?>, List<BackwardField>>(); this.useDomainLabels = false; this.version = -1; } private int getVersion() { return version; } @SuppressWarnings("unchecked") private void initFrom(GrNode rInfo) { GrProperty prop = rInfo.getProperty(DomainAccessHandler.DomainInfoUseDomainLabelProperty); if (prop != null) { this.useDomainLabels = (boolean) prop.getValue(); } prop = rInfo.getProperty(DomainAccessHandler.DomainInfoLabel2ClassProperty); List<String> c2l_list = null; if (prop != null) c2l_list = (List<String>) prop.getValue(); prop = rInfo.getProperty(DomainAccessHandler.DomainInfoFieldComponentTypeProperty); List<String> compType_list = null; if (prop != null) compType_list = (List<String>) prop.getValue(); prop = rInfo.getProperty(DomainAccessHandler.DomainInfoConcreteFieldTypeProperty); List<String> concType_list = null; if (prop != null) concType_list = (List<String>) prop.getValue(); if (c2l_list != null) { for (String str : c2l_list) { String[] c2l = str.split("="); try { Class<?> clazz = domainAccessHandler.domainModel.getClassForName(c2l[1]); this.addClassLabel(clazz, c2l[0]); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } } if (compType_list != null) { for (String str : compType_list) { String[] c2l = str.split("="); String[] classes = c2l[1].split(CompoundObjectType.SEPARATOR); for (String cls : classes) { try { Class<?> clazz = domainAccessHandler.domainModel.getClassForName(cls); this.addFieldComponentType(c2l[0], clazz); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } } } if (concType_list != null) { for (String str : concType_list) { String[] c2l = str.split("="); String[] classes = c2l[1].split(CompoundObjectType.SEPARATOR); for (String cls : classes) { try { Class<?> clazz = domainAccessHandler.domainModel.getClassForName(cls); this.addConcreteFieldType(c2l[0], clazz); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } } } prop = rInfo.getProperty(DomainAccessHandler.DomainInfoVersionProperty); if (prop != null) this.version = ((Number)prop.getValue()).intValue(); else this.version = 0; prop = rInfo.getProperty(DomainAccessHandler.DomainInfoModelVersionProperty); if (prop != null) DomainAccess.this.domainAccessHandler.domainModel.setVersion(((Number)prop.getValue()).intValue()); else DomainAccess.this.domainAccessHandler.domainModel.setVersion(0); this.setChanged(false); } private void updateFrom(DomainInfo dInfo) { Iterator<Entry<Class<?>, String>> it = dInfo.class2labelMap.entrySet().iterator(); while (it.hasNext()) { Entry<Class<?>, String> entry = it.next(); this.addClassLabel(entry.getKey(), entry.getValue()); } Iterator<Entry<String, CompoundObjectType>> it2 = dInfo.fieldComponentTypeMap.entrySet().iterator(); while(it2.hasNext()) { Entry<String, CompoundObjectType> entry = it2.next(); Iterator<CompoundObjectType> it3 = entry.getValue().typeIterator(); while(it3.hasNext()) { CompoundObjectType cType = it3.next(); this.addFieldComponentType(entry.getKey(), cType.getType()); } } it2 = dInfo.concreteFieldTypeMap.entrySet().iterator(); while(it2.hasNext()) { Entry<String, CompoundObjectType> entry = it2.next(); Iterator<CompoundObjectType> it3 = entry.getValue().typeIterator(); while(it3.hasNext()) { CompoundObjectType cType = it3.next(); this.addConcreteFieldType(entry.getKey(), cType.getType()); } } } private boolean isChanged() { return changed; } private void setChanged(boolean changed) { if (!this.changed && changed) this.version++; this.changed = changed; } private void graphUdated() { if (this.changed) { ITransaction tx = domainAccessHandler.dbAccess.getTX(); if (tx != null) ((AbstractTransaction)tx).setDomainInfoChanged(); } this.setChanged(false); } private void addClassLabel(Class<?> clazz, String label) { if (!this.class2labelMap.containsKey(clazz)) { this.class2labelMap.put(clazz, label); this.label2ClassMap.put(label, clazz); this.setChanged(true); } } private void addFieldComponentType(String classField, Class<?> clazz) { CompoundObjectType cType = this.fieldComponentTypeMap.get(classField); if (cType == null) { cType = new CompoundObjectType(clazz); this.fieldComponentTypeMap.put(classField, cType); this.setChanged(true); } else { boolean added = cType.addType(clazz); this.setChanged(this.changed || added); } // add for backward navigation BackwardField bwf = new BackwardField(classField); List<BackwardField> bwfs = this.componentTypeBackward.get(clazz); if (bwfs == null) { bwfs = new ArrayList<BackwardField>(); this.componentTypeBackward.put(clazz, bwfs); } if (!bwfs.contains(bwf)) bwfs.add(bwf); } private CompoundObjectType getFieldComponentType(String classField) { return this.fieldComponentTypeMap.get(classField); } private List<BackwardField> getBackwardFields(Class<?> clazz) { return this.fieldTypeBackward.get(clazz); } private List<BackwardField> getBackwardComponentFields(Class<?> clazz) { return this.componentTypeBackward.get(clazz); } private void addConcreteFieldType(String classField, Class<?> clazz) { CompoundObjectType cType = this.concreteFieldTypeMap.get(classField); if (cType == null) { cType = new CompoundObjectType(clazz); this.concreteFieldTypeMap.put(classField, cType); this.setChanged(true); } else { boolean added = cType.addType(clazz); this.setChanged(this.changed || added); } // add for backward navigation BackwardField bwf = new BackwardField(classField); List<BackwardField> bwfs = this.fieldTypeBackward.get(clazz); if (bwfs == null) { bwfs = new ArrayList<BackwardField>(); this.fieldTypeBackward.put(clazz, bwfs); } if (!bwfs.contains(bwf)) bwfs.add(bwf); } private CompoundObjectType getConcreteFieldType(String classField) { return this.concreteFieldTypeMap.get(classField); } private Class<?> getClassForLabel(String label) { return this.label2ClassMap.get(label); } private String getLabelForClass(Class<?> clazz) { return this.class2labelMap.get(clazz); } private Set<Class<?>> getAllStoredDomainClasses() { return this.class2labelMap.keySet(); } private List<String> getLabel2ClassNameStringList() { List<String> ret = new ArrayList<String>(this.class2labelMap.size()); Iterator<Entry<Class<?>, String>> it = this.class2labelMap.entrySet().iterator(); while(it.hasNext()) { Entry<Class<?>, String> entry = it.next(); StringBuilder sb = new StringBuilder(); sb.append(entry.getValue()); sb.append('='); sb.append(entry.getKey().getName()); ret.add(sb.toString()); } Collections.sort(ret); return ret; } private List<String> getFieldComponentTypeStringList() { List<String> ret = new ArrayList<String>(this.fieldComponentTypeMap.size()); Iterator<Entry<String, CompoundObjectType>> it = this.fieldComponentTypeMap.entrySet().iterator(); while(it.hasNext()) { Entry<String, CompoundObjectType> entry = it.next(); StringBuilder sb = new StringBuilder(); sb.append(entry.getKey()); sb.append('='); sb.append(entry.getValue().getTypeListString()); ret.add(sb.toString()); } Collections.sort(ret); return ret; } private List<String> getConcreteFieldTypeStringList() { List<String> ret = new ArrayList<String>(this.concreteFieldTypeMap.size()); Iterator<Entry<String, CompoundObjectType>> it = this.concreteFieldTypeMap.entrySet().iterator(); while(it.hasNext()) { Entry<String, CompoundObjectType> entry = it.next(); StringBuilder sb = new StringBuilder(); sb.append(entry.getKey()); sb.append('='); sb.append(entry.getValue().getTypeListString()); ret.add(sb.toString()); } Collections.sort(ret); return ret; } /********************************/ private class BackwardField { private Class<?> sourceClass; private String sourceFieldName; private BackwardField(String classField) { super(); String[] clField = classField.split(domainAccessHandler.regexClassfieldSep); try { this.sourceClass = domainAccessHandler.domainModel.getClassForName(clField[0]); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } this.sourceFieldName = clField[1]; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((sourceClass == null) ? 0 : sourceClass.hashCode()); result = prime * result + ((sourceFieldName == null) ? 0 : sourceFieldName .hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; BackwardField other = (BackwardField) obj; if (sourceClass == null) { if (other.sourceClass != null) return false; } else if (!sourceClass.equals(other.sourceClass)) return false; if (sourceFieldName == null) { if (other.sourceFieldName != null) return false; } else if (!sourceFieldName.equals(other.sourceFieldName)) return false; return true; } } } /********************************/ private class SurrogateChangeLog { private Set<IRelation> added = new HashSet<IRelation>(); private Set<IRelation> removed = new HashSet<IRelation>(); private void applyChanges() { removed.removeAll(added); DomainState ds = domainAccessHandler.getDomainState(); for (IRelation rel : added) { ds.getSurrogateState() .addReference(rel); } for (IRelation rel : removed) { ds.getSurrogateState() .removeReference(rel); } ds.getSurrogateState().removeUnreferenced(); } } /**********************************/ private enum Exec { INIT_LOADED, TRIED_STORE, RELOAD_STORE, RELOADED_STORE, RELOAD, RELOADED, STORE_VERSIONS } /**********************************/ private class ExecContext { private Exec dInfo; private Exec dModel; } /**********************************/ private class ReResolve { private Set<Object> reResolved = new HashSet<>(); } /************************************/ public interface IRecursionExit { public void addRecursionExitObject(Object obj, int resolvedDepth); } /***********************************/ public class InternalDomainAccess { private Object syncObject; private InternalDomainAccess() { super(); } public CompoundObjectType getFieldComponentType(String classField) { DomainInfo di = domainAccessHandler.loadDomainInfoIfNeeded(); return di.getFieldComponentType(classField); } public CompoundObjectType getConcreteFieldType(String classField) { DomainInfo di = domainAccessHandler.loadDomainInfoIfNeeded(); return di.getConcreteFieldType(classField); } public void addFieldComponentType(String classField, Class<?> type) { DomainInfo di = domainAccessHandler.getAvailableDomainInfo(); di.addFieldComponentType(classField, type); } public void addConcreteFieldType(String classField, Class<?> type) { DomainInfo di = domainAccessHandler.getAvailableDomainInfo(); di.addConcreteFieldType(classField, type); } public DomainState getDomainState() { return domainAccessHandler.getDomainState(); } public ObjectMapping getObjectMappingFor(Class<?> domainObjectType) { domainAccessHandler.updateMappingsIfNeeded(); return domainAccessHandler.getObjectMappingFor(domainObjectType); } public List<FieldMapping> getBackwardFieldMappings(String attribName, Class<?> domainObjectType) { List<FieldMapping> ret = new ArrayList<FieldMapping>(); List<DomainInfo.BackwardField> bwfs = domainAccessHandler.domainInfo.getBackwardFields(domainObjectType); if (bwfs != null) { for (DomainInfo.BackwardField bwf : bwfs) { if (bwf.sourceFieldName.equals(attribName)) { FieldMapping fm = this.getObjectMappingFor(bwf.sourceClass).getFieldMappingForField(attribName); if (fm != null) ret.add(fm); } } } // add if navigated via a surrogate (e.g. Collection) bwfs = domainAccessHandler.domainInfo.getBackwardComponentFields(domainObjectType); if (bwfs != null) { for (DomainInfo.BackwardField bwf : bwfs) { if (this.isBackwardViaSurrogate(attribName, bwf)) { FieldMapping fm = this.getObjectMappingFor(bwf.sourceClass) .getFieldMappingForField(bwf.sourceFieldName); if (fm != null) ret.add(fm); } } } return ret; } private boolean isBackwardViaSurrogate(String attribName, DomainInfo.BackwardField backwardField) { List<DomainInfo.BackwardField> bwfs = domainAccessHandler.domainInfo.getBackwardFields(backwardField.sourceClass); if (bwfs != null) { for (DomainInfo.BackwardField bwf : bwfs) { if (bwf.sourceFieldName.equals(attribName)) { return true; } } } return false; } private void addBackwardFieldMappings(String attribName, Class<?> domainObjectType, List<FieldMapping> fms) { List<DomainInfo.BackwardField> bwfs = domainAccessHandler.domainInfo.getBackwardFields(domainObjectType); if (bwfs != null) { for (DomainInfo.BackwardField bwf : bwfs) { if (bwf.sourceFieldName.equals(attribName)) { FieldMapping fm = this.getObjectMappingFor(bwf.sourceClass).getFieldMappingForField(attribName); if (fm != null) fms.add(fm); } } } } public List<Class<?>> getCompoundTypesFor(Class<?> domainObjectType) { return domainAccessHandler.getCompoundTypesFor(domainObjectType); } public String getLabelForClass(Class<?> clazz) { domainAccessHandler.updateMappingsIfNeeded(); return domainAccessHandler.domainInfo.getLabelForClass(clazz); } public Class<?> getClassForLabel(String label) { domainAccessHandler.updateMappingsIfNeeded(); return domainAccessHandler.domainInfo.getClassForLabel(label); } public boolean existsLabel(String label) { domainAccessHandler.updateMappingsIfNeeded(); return domainAccessHandler.domainInfo.getClassForLabel(label) != null; } public List<JcQueryResult> execute(List<JcQuery> queries) { return domainAccessHandler.dbAccess.execute(queries); } public <T> List<T> loadByIds(Class<T> domainObjectClass, Map<Class<?>, List<Long>> type2IdsMap, int resolutionDepth, long... ids) { return domainAccessHandler.loadByIds(domainObjectClass, type2IdsMap, resolutionDepth, ids); } public void replace_Id2Object(InnerClassSurrogate surrogate, Object domainObject, long nodeId) { domainAccessHandler.getDomainState().replace_Id2Object(surrogate, domainObject, nodeId); } public String setDomainLabel() { return domainAccessHandler.setDomainLabel(); } public void transactionClosed(boolean failed, boolean domainInfoChanged, boolean noInfoNodeId) { synchronized (domainAccessHandler) { DomainState ds = domainAccessHandler.transactionState.get(); if (ds != null) { domainAccessHandler.transactionState.remove(); if (!failed) { domainAccessHandler.domainState = ds; } else { // failed (rollBack) if (domainInfoChanged) { domainAccessHandler.domainInfo.setChanged(true); if (noInfoNodeId) domainAccessHandler.domainInfo.nodeId = -1; } } } domainAccessHandler.domainModel.closeTx(failed); } } public Class<?> getClassForName(String name) throws ClassNotFoundException { return domainAccessHandler.domainModel.getClassForName(name); } public List<DomainObject> getGenericDomainObjects(List<?> objects) { return genericDomainAccess.getDomainObjects(objects); } public DomainObject getGenericDomainObject(Object object) { return genericDomainAccess.getDomainObject(object); } public void setQExecution(QExecution qExecution) { if (qExecution == null) DomainAccess.qExecution.remove(); else DomainAccess.qExecution.set(qExecution); } public QExecution getQExecution() { return DomainAccess.qExecution.get(); } public void startReResolve() { domainAccessHandler.reResolve.set(new ReResolve()); } public void endReResolve() { domainAccessHandler.reResolve.remove(); } /** * For Testing */ public void loadDomainInfoIfNeeded() { domainAccessHandler.loadDomainInfoIfNeeded(); } public DomainQuery createRecordedQuery(ReplayedQueryContext rqc, boolean doRecord) { return DomainAccess.this.createRecordedQuery(rqc, doRecord); } public GDomainQuery createRecordedGenQuery(ReplayedQueryContext rqc, boolean doRecord) { return ((GenericDomainAccess)DomainAccess.this.getGenericDomainAccess()).createRecordedQuery(rqc, doRecord); } public Object getSyncObject() { return syncObject; } public void setSyncObject(Object syncObject) { this.syncObject = syncObject; } public DomainModel getDomainModel() { return domainAccessHandler.getDomainModel(); } public String buildDomainLabel(String domainName) { String ret = domainName.replace('-', '_').replace(' ', '_'); // also replace blanks return ret; } public String getDomainLabel() { return domainAccessHandler.getDomainLabel(); } public IDBAccess getDBAccess() { return ((DomainAccessHandler.DBAccessWrapper)domainAccessHandler.dbAccess).delegate; } /** * For Testing * @return */ public String domainModelAsString() { return domainAccessHandler.getDomainModel().asString(); } /** * For Testing * @return */ public int getDomainInfoVersion() { if (domainAccessHandler.domainInfo == null) return -1; return domainAccessHandler.domainInfo.version; } /** * For Testing * @return */ public String nurseryAsString() { return domainAccessHandler.domainModel.nurseryAsString(); } } }