/************************************************************************
* 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.domainquery;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import iot.jcypher.domain.IDomainAccess;
import iot.jcypher.domain.genericmodel.DomainObject;
import iot.jcypher.domain.genericmodel.InternalAccess;
import iot.jcypher.domain.internal.DomainAccess.InternalDomainAccess;
import iot.jcypher.domainquery.api.APIAccess;
import iot.jcypher.domainquery.api.BooleanOperation;
import iot.jcypher.domainquery.api.Collect;
import iot.jcypher.domainquery.api.DomainObjectMatch;
import iot.jcypher.domainquery.api.IPredicateOperand1;
import iot.jcypher.domainquery.api.Order;
import iot.jcypher.domainquery.api.Select;
import iot.jcypher.domainquery.api.TerminalResult;
import iot.jcypher.domainquery.api.Traverse;
import iot.jcypher.domainquery.ast.CollectExpression;
import iot.jcypher.domainquery.ast.ConcatenateExpression;
import iot.jcypher.domainquery.ast.ConcatenateExpression.Concatenator;
import iot.jcypher.domainquery.ast.FromPreviousQueryExpression;
import iot.jcypher.domainquery.ast.IASTObject;
import iot.jcypher.domainquery.ast.OrderExpression;
import iot.jcypher.domainquery.ast.Parameter;
import iot.jcypher.domainquery.ast.PredicateExpression;
import iot.jcypher.domainquery.ast.SelectExpression;
import iot.jcypher.domainquery.ast.TraversalExpression;
import iot.jcypher.domainquery.ast.UnionExpression;
import iot.jcypher.domainquery.internal.IASTObjectsContainer;
import iot.jcypher.domainquery.internal.QueryExecutor;
import iot.jcypher.domainquery.internal.QueryRecorder;
import iot.jcypher.domainquery.internal.RecordedQuery;
import iot.jcypher.domainquery.internal.ReplayedQueryContext;
import iot.jcypher.query.values.JcProperty;
public abstract class AbstractDomainQuery {
protected QueryExecutor queryExecutor;
private IASTObjectsContainer astObjectsContainer;
private IntAccess intAccess;
public AbstractDomainQuery(IDomainAccess domainAccess) {
super();
this.queryExecutor = new QueryExecutor(domainAccess);
this.astObjectsContainer = this.queryExecutor;
}
void recordQuery(RecordedQuery rq) {
this.queryExecutor.recordQuery(rq, this);
}
void replayQuery(ReplayedQueryContext rqc) {
this.queryExecutor.replayQuery(rqc);
}
/**
* Create a match for a specific type of domain objects
* @param domainObjectType
* @return a DomainObjectMatch for a specific type of domain objects
*/
<T> DomainObjectMatch<T> createMatchInternal(Class<T> domainObjectType) {
DomainObjectMatch<T> ret =APIAccess.createDomainObjectMatch(domainObjectType,
this.queryExecutor.getDomainObjectMatches().size(),
this.queryExecutor.getMappingInfo());
this.queryExecutor.getDomainObjectMatches().add(ret);
return ret;
}
/**
* Create a match from a DomainObjectMatch specified in the context of another query
* @param domainObjectMatch a match specified in the context of another query
* @return a DomainObjectMatch
*/
@SuppressWarnings("unchecked")
public <T> DomainObjectMatch<T> createMatchFrom(DomainObjectMatch<T> domainObjectMatch) {
DomainObjectMatch<T> ret;
FromPreviousQueryExpression pqe;
DomainObjectMatch<?> match;
DomainObjectMatch<?> delegate = APIAccess.getDelegate(domainObjectMatch);
if (delegate != null) { // generic model
DomainObjectMatch<?> newDelegate = APIAccess.createDomainObjectMatch(delegate,
this.queryExecutor.getDomainObjectMatches().size(),
this.queryExecutor.getMappingInfo());
this.queryExecutor.getDomainObjectMatches().add(newDelegate);
pqe = new FromPreviousQueryExpression(
newDelegate, delegate);
ret = (DomainObjectMatch<T>) APIAccess.createDomainObjectMatch(DomainObject.class, newDelegate);
match = newDelegate;
} else {
ret = APIAccess.createDomainObjectMatch(domainObjectMatch,
this.queryExecutor.getDomainObjectMatches().size(),
this.queryExecutor.getMappingInfo());
this.queryExecutor.getDomainObjectMatches().add(ret);
pqe = new FromPreviousQueryExpression(
ret, domainObjectMatch);
match = ret;
}
this.queryExecutor.addAstObject(pqe);
QueryRecorder.recordAssignment(this, "createMatchFrom", match,
QueryRecorder.reference(domainObjectMatch));
return ret;
}
/**
* Create a match for a domain object which was retrieved by another query
* @param domainObject a domain object which was retrieved by another query
* @return a DomainObjectMatch
*/
@SuppressWarnings("unchecked")
public <T> DomainObjectMatch<T> createMatchFor(T domainObject) {
DomainObjectMatch<T> ret;
if (domainObject.getClass().equals(DomainObject.class)) { // generic model
List<DomainObject> source = new ArrayList<DomainObject>();
source.add((DomainObject) domainObject);
String typeName = ((DomainObject)domainObject).getDomainObjectType().getName();
ret = (DomainObjectMatch<T>) createGenMatchForInternal(source, typeName);
} else {
List<T> source = new ArrayList<T>();
source.add(domainObject);
ret = this.createMatchForInternal(source, (Class<T>)domainObject.getClass());
}
DomainObjectMatch<?> delegate = APIAccess.getDelegate(ret);
DomainObjectMatch<?> match = delegate != null ? delegate : ret;
QueryRecorder.recordAssignment(this, "createMatchFor", match,
QueryRecorder.reference(domainObject));
return ret;
}
/**
* Create a match for a list of domain objects which were retrieved by another query
* @param domainObjects a list of domain objects which were retrieved by another query
* @param domainObjectType the type of those domain objects
* @return a DomainObjectMatch
*/
protected <T> DomainObjectMatch<T> createMatchForInternal(List<T> domainObjects,
Class<T> domainObjectType) {
DomainObjectMatch<T> ret = APIAccess.createDomainObjectMatch(domainObjectType,
this.queryExecutor.getDomainObjectMatches().size(),
this.queryExecutor.getMappingInfo());
this.queryExecutor.getDomainObjectMatches().add(ret);
FromPreviousQueryExpression pqe = new FromPreviousQueryExpression(
ret, domainObjects);
this.queryExecutor.addAstObject(pqe);
return ret;
}
/**
* Create a match for a list of domain objects which were retrieved by another query.
* <br/>The match will be part of a query performed on a generic domain model.
* @param domainObjects a list of domain objects which were retrieved by another query
* @param domainObjectTypeName the type name of those domain objects
* @return a DomainObjectMatch
*/
protected DomainObjectMatch<DomainObject> createGenMatchForInternal(List<DomainObject> domainObjects,
String domainObjectTypeName) {
InternalDomainAccess iAccess = this.queryExecutor.getMappingInfo().getInternalDomainAccess();
try {
iAccess.loadDomainInfoIfNeeded();
List<Object> dobjs = new ArrayList<Object>();
for (DomainObject dobj : domainObjects) {
dobjs.add(InternalAccess.getRawObject(dobj));
}
@SuppressWarnings("rawtypes")
Class clazz = iAccess.getClassForName(domainObjectTypeName);
@SuppressWarnings("unchecked")
DomainObjectMatch<?> delegate = createMatchForInternal(dobjs, clazz);
DomainObjectMatch<DomainObject> ret = APIAccess.createDomainObjectMatch(DomainObject.class, delegate);
return ret;
} catch (Throwable e) {
if (e instanceof RuntimeException)
throw (RuntimeException)e;
else
throw new RuntimeException(e);
}
}
/**
* Get or create, if not exists, a query parameter.
* @param name of the parameter
* @return a query parameter
*/
public Parameter parameter(String name) {
return this.queryExecutor.parameter(name);
}
public List<String> getParameterNames() {
return new ArrayList<String>(this.queryExecutor.getParameterNames());
}
/**
* Start formulating a predicate expression.
* A predicate expression yields a boolean value.
* <br/>Takes an expression like 'person.stringAttribute("name")', yielding an attribute,
* <br/>e.g. WHERE(person.stringAttribute("name")).EQUALS(...)
* @param value the value(expression) to formulate the predicate expression upon.
* @return
*/
public BooleanOperation WHERE(IPredicateOperand1 value) {
IPredicateOperand1 pVal = value;
if (value instanceof DomainObjectMatch<?>) {
DomainObjectMatch<?> delegate = APIAccess.getDelegate((DomainObjectMatch<?>) value);
if (delegate != null) // generic model
pVal = delegate;
}
PredicateExpression pe = new PredicateExpression(pVal, this.astObjectsContainer);
this.astObjectsContainer.addAstObject(pe);
BooleanOperation ret = APIAccess.createBooleanOperation(pe);
QueryRecorder.recordInvocation(this, "WHERE", ret, QueryRecorder.placeHolder(pVal));
return ret;
}
/**
* Or two predicate expressions
*/
public TerminalResult OR() {
ConcatenateExpression ce = new ConcatenateExpression(Concatenator.OR);
this.astObjectsContainer.addAstObject(ce);
TerminalResult ret = APIAccess.createTerminalResult(ce);
QueryRecorder.recordInvocation(this, "OR", ret);
return ret;
}
/**
* Open a block, encapsulating predicate expressions
*/
public TerminalResult BR_OPEN() {
ConcatenateExpression ce = new ConcatenateExpression(Concatenator.BR_OPEN);
this.astObjectsContainer.addAstObject(ce);
TerminalResult ret = APIAccess.createTerminalResult(ce);
QueryRecorder.recordInvocation(this, "BR_OPEN", ret);
return ret;
}
/**
* Close a block, encapsulating predicate expressions
*/
public TerminalResult BR_CLOSE() {
ConcatenateExpression ce = new ConcatenateExpression(Concatenator.BR_CLOSE);
this.astObjectsContainer.addAstObject(ce);
TerminalResult ret = APIAccess.createTerminalResult(ce);
QueryRecorder.recordInvocation(this, "BR_CLOSE", ret);
return ret;
}
/**
* Define an order on a set of domain objects which are specified by
* a DomainObjectMatch in the context of the domain query.
* @param toOrder the DomainObjectMatch
* specifying the set of domain objects which should be ordered
* @return
*/
public Order ORDER(DomainObjectMatch<?> toOrder) {
DomainObjectMatch<?> delegate = APIAccess.getDelegate(toOrder);
DomainObjectMatch<?> match = delegate != null ? delegate : toOrder;
OrderExpression oe = this.queryExecutor.getOrderFor(match);
Order ret = APIAccess.createOrder(oe);
QueryRecorder.recordInvocation(this, "ORDER", ret, QueryRecorder.placeHolder(match));
return ret;
}
/**
* Start traversing the graph of domain objects.
* @param start a DomainObjectMatch form where to start the traversal.
* @return
*/
public Traverse TRAVERSE_FROM(DomainObjectMatch<?> start) {
DomainObjectMatch<?> delegate = APIAccess.getDelegate(start);
DomainObjectMatch<?> match = delegate != null ? delegate : start;
TraversalExpression te = new TraversalExpression(match, this.queryExecutor);
this.queryExecutor.addAstObject(te);
Traverse ret = APIAccess.createTraverse(te);
QueryRecorder.recordInvocation(this, "TRAVERSE_FROM", ret, QueryRecorder.placeHolder(match));
return ret;
}
/**
* Select domain objects out of a set of other domain objects.
* @param start with a DomainObjectMatch representing the initial set.
* @return
*/
public <T> Select<T> SELECT_FROM(DomainObjectMatch<T> start) {
DomainObjectMatch<?> delegate = APIAccess.getDelegate(start);
DomainObjectMatch<?> match = delegate != null ? delegate : start;
SelectExpression<T> se = new SelectExpression<T>(APIAccess.getDomainObjectType(start),
match, this.getIntAccess());
this.queryExecutor.addAstObject(se);
this.astObjectsContainer = se;
Select<T> ret = APIAccess.createSelect(se, getIntAccess());
QueryRecorder.recordInvocation(this, "SELECT_FROM", ret, QueryRecorder.placeHolder(match));
return ret;
}
/**
* Reject domain objects from a set of domain objects.
* Answer a set containing all objects of the source set except the rejected ones.
* @param start with a DomainObjectMatch representing the initial set.
* @return
*/
public <T> Select<T> REJECT_FROM(DomainObjectMatch<T> start) {
DomainObjectMatch<?> delegate = APIAccess.getDelegate(start);
DomainObjectMatch<?> match = delegate != null ? delegate : start;
SelectExpression<T> se = new SelectExpression<T>(APIAccess.getDomainObjectType(start),
match, this.getIntAccess(), true);
this.queryExecutor.addAstObject(se);
this.astObjectsContainer = se;
Select<T> ret = APIAccess.createSelect(se, getIntAccess());
QueryRecorder.recordInvocation(this, "REJECT_FROM", ret, QueryRecorder.placeHolder(match));
return ret;
}
/**
* Collect the specified attribute from all objects in a DomainObjectMatch
* @param attribute
* @return
*/
public Collect COLLECT(JcProperty attribute) {
CollectExpression ce = new CollectExpression(attribute, this.getIntAccess());
Collect coll = APIAccess.createCollect(ce);
this.queryExecutor.addAstObject(ce);
QueryRecorder.recordInvocation(this, "COLLECT", coll, QueryRecorder.placeHolder(attribute));
return coll;
}
/**
* Build the union of the specified sets
* @param set
* @return
*/
@SuppressWarnings("unchecked")
public <T> DomainObjectMatch<T> UNION(DomainObjectMatch<T>... set) {
DomainObjectMatch<T> ret = this.union_Intersection(true, set);
Object[] placeHolders = new Object[set.length];
DomainObjectMatch<?> delegate;
DomainObjectMatch<?> match;
for (int i = 0; i < set.length; i++) {
delegate = APIAccess.getDelegate(set[i]);
match = delegate != null ? delegate : set[i];
placeHolders[i] = QueryRecorder.placeHolder(match);
}
delegate = APIAccess.getDelegate(ret);
match = delegate != null ? delegate : ret;
QueryRecorder.recordAssignment(this, "UNION", match, placeHolders);
return ret;
}
/**
* Build the intersection of the specified sets
* @param set
* @return
*/
@SuppressWarnings("unchecked")
public <T> DomainObjectMatch<T> INTERSECTION(DomainObjectMatch<T>... set) {
DomainObjectMatch<T> ret = this.union_Intersection(false, set);
Object[] placeHolders = new Object[set.length];
DomainObjectMatch<?> delegate;
DomainObjectMatch<?> match;
for (int i = 0; i < set.length; i++) {
delegate = APIAccess.getDelegate(set[i]);
match = delegate != null ? delegate : set[i];
placeHolders[i] = QueryRecorder.placeHolder(match);
}
delegate = APIAccess.getDelegate(ret);
match = delegate != null ? delegate : ret;
QueryRecorder.recordAssignment(this, "INTERSECTION", match, placeHolders);
return ret;
}
/**
* Execute the domain query
* @return a DomainQueryResult
*/
public DomainQueryResult execute() {
DomainQueryResult ret = new DomainQueryResult(this);
Object so = this.queryExecutor.getMappingInfo().getInternalDomainAccess().getSyncObject();
if (so != null) {
synchronized (so) {
this.queryExecutor.execute();
}
} else
this.queryExecutor.execute();
return ret;
}
/**
* Retrieve the count for every DomainObjectMatch of the query
* in order to support pagination
* @return a CountQueryResult
*/
public CountQueryResult executeCount() {
CountQueryResult ret = new CountQueryResult(this);
Object so = this.queryExecutor.getMappingInfo().getInternalDomainAccess().getSyncObject();
if (so != null) {
synchronized (so) {
this.queryExecutor.executeCount();
}
} else
this.queryExecutor.executeCount();
return ret;
}
/**
* Answer the context containing DomainObjectMatch(es) of a replayed query.
* <br/>Answer null, if this is not a replayed query.
* @return
*/
public ReplayedQueryContext getReplayedQueryContext() {
return this.queryExecutor.getReplayedQueryContext();
}
/**
* Answer the recorded query. May return null.
* @return
*/
public RecordedQuery getRecordedQuery() {
return this.queryExecutor.getRecordedQuery();
}
/**
* answer a map containing DomainObjectMatch to id entries
* @return
*/
Map<Object, String> getRecordedQueryObjects() {
return this.queryExecutor.getRecordedQueryObjects();
}
QueryExecutor getQueryExecutor() {
return this.queryExecutor;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private <T> DomainObjectMatch<T> union_Intersection(boolean union, DomainObjectMatch<T>... set) {
DomainObjectMatch<T> ret;
Boolean br_old = QueryRecorder.blockRecording.get();
try {
QueryRecorder.blockRecording.set(Boolean.TRUE);
DomainObjectMatch[] newSet = set;
boolean isGeneric = set.length > 0 && APIAccess.getDelegate(set[0]) != null;
if (isGeneric) {
newSet = new DomainObjectMatch[set.length];
for (int i = 0; i < set.length; i++) {
DomainObjectMatch<?> delegate = APIAccess.getDelegate(set[i]);
if (delegate != null) // generic model
newSet[i] = delegate;
else
newSet[i] = set[i];
}
}
DomainObjectMatch newMatch = build_union_Intersection(union, newSet);
if (isGeneric)
ret = (DomainObjectMatch<T>) APIAccess.createDomainObjectMatch(DomainObject.class, newMatch);
else
ret = newMatch;
} finally {
QueryRecorder.blockRecording.set(br_old);
}
return ret;
}
@SuppressWarnings("unchecked")
private <T> DomainObjectMatch<T> build_union_Intersection(boolean union, DomainObjectMatch<T>... set) {
DomainObjectMatch<T> ret = null;
if (set.length > 0) {
IASTObject lastOne = null;
UnionExpression ue = new UnionExpression(union);
ret =APIAccess.createDomainObjectMatch(APIAccess.getDomainObjectType(set[0]),
this.queryExecutor.getDomainObjectMatches().size(),
this.queryExecutor.getMappingInfo());
this.queryExecutor.getDomainObjectMatches().add(ret);
ue.setResult(ret);
APIAccess.setUnionExpression(ret, ue);
int idx = 0;
if (set.length > 1)
this.BR_OPEN();
for (DomainObjectMatch<T> dom : set) {
ue.getSources().add(dom);
if (idx > 0 && union)
this.OR();
lastOne = APIAccess.getAstObject(this.WHERE(ret).IN(dom));
idx++;
}
if (set.length > 1)
lastOne = APIAccess.getAstObject(this.BR_CLOSE());
ue.setLastOfUnionBase(lastOne);
}
return ret;
}
private IntAccess getIntAccess() {
if (this.intAccess == null)
this.intAccess = new IntAccess();
return this.intAccess;
}
/****************************************************/
public class IntAccess {
public QueryExecutor getQueryExecutor() {
return queryExecutor;
}
public AbstractDomainQuery getDomainQuery() {
return AbstractDomainQuery.this;
}
public void resetAstObjectsContainer() {
astObjectsContainer = queryExecutor;
}
}
}