
tools.xor.view.QueryTreeInvocation Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of xor Show documentation
Show all versions of xor Show documentation
Empowering Model Driven Architecture in J2EE applications
/**
* XOR, empowering Model Driven Architecture in J2EE applications
*
* Copyright (c) 2019, Dilip Dalton
*
* 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 tools.xor.view;
import java.nio.ByteBuffer;
import java.util.Base64;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
import tools.xor.AbstractTypeMapper;
import tools.xor.BusinessObject;
import tools.xor.EntityKey;
import tools.xor.SurrogateEntityKey;
import tools.xor.Type;
import tools.xor.util.InterQuery;
/**
* Used to hold temporary results during the execution of a QueryTree
*/
public class QueryTreeInvocation
{
public static final int MAX_INLIST_SIZE = 999;
public static final int OFFSET = 1;
private Map parentIdList; // Return the ids needed for a consuming Query
// Can be userkey, if it is not composite, else
// subquery is the only option supported for composite key.
// A QueryTree can have different sets of parentIdList since
// 2 InterQuery edges might have different source fragments
private Map lastParentId; // useful for result scrolling functionality
private Map idList; // ids needed for the QueryFragment.PARENT_INLIST parameter
// If a query does not have an entry here after resolveQuery
// has been processed, that means it is a SUBQUERY join till
// the root node
private Map> objectsByPath; // Used for stitching child objects
// The parent objects are obtained by getting
// the full path from the source fragment of the InterQuery edge
private Map queryObjects; // Unique object per id and path
private Map> recordDeltas;
private Map visitors; // used during a QueryTree's resolveField calls
private Map visitorsByPath;
private Map invocationIds; // Safe to use QueryTree as we use a copy of the AggregateTree
private static final Base64.Encoder BASE64_URL_ENCODER = Base64.getUrlEncoder().withoutPadding();
public QueryTreeInvocation() {
// These fields will concurrently be updated/accessed if using ParallelDispatcher
this.parentIdList = new ConcurrentHashMap<>();
this.lastParentId = new ConcurrentHashMap<>();
this.idList = new ConcurrentHashMap<>();
this.visitors = new ConcurrentHashMap<>();
this.visitorsByPath = new ConcurrentHashMap<>();
this.objectsByPath = new ConcurrentHashMap<>();
this.queryObjects = new ConcurrentHashMap<>();
this.recordDeltas = new ConcurrentHashMap<>();
this.invocationIds = new ConcurrentHashMap<>();
}
public String getOrCreateInvocationId (QueryTree queryTree)
{
if(!invocationIds.containsKey(queryTree)) {
// generate a GUID invocation id that is 128 bits in length in base64 format
UUID uuid = UUID.randomUUID();
byte[] bytes = getBytesFromUUID(uuid);
invocationIds.put(queryTree, BASE64_URL_ENCODER.encodeToString(bytes));
}
return invocationIds.get(queryTree);
}
public String getInvocationId (QueryTree queryTree) {
return invocationIds.get(queryTree);
}
public Object getLastParentId(QueryTree queryTree) {
return this.lastParentId.get(queryTree);
}
public Set getParentIds(InterQuery edge) {
return this.parentIdList.get(edge.getSource());
}
public static byte[] getBytesFromUUID(UUID uuid) {
ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
bb.putLong(uuid.getMostSignificantBits());
bb.putLong(uuid.getLeastSignificantBits());
return bb.array();
}
public static class RecordDelta {
private Set changed; // the fields that have new information
private Map propertyResult; // information from result set keyed by full path
private String lcp;
private Object[] record;
public RecordDelta(Set changed, Map propertyResult, String lcp, Object[] record) {
this.changed = changed;
this.propertyResult = propertyResult;
this.lcp = lcp;
this.record = record;
}
public Set getChanged() {
return this.changed;
}
public Map getPropertyResult() {
return this.propertyResult;
}
public String getLCP() {
return this.lcp;
}
public Object[] getRecord() {
return this.record;
}
}
/**
* Update the query string for the child query
*
* @param edge for which the query string needs to be updated for the edge end
*/
public void resolveQuery(InterQuery edge) {
QueryTree queryTree = edge.getEnd();
InterQuery parentEdge = edge;
// since query is resolved in a BFS manner, we only need to join with the immediate parent
InterQuery.JoinType joinType = getJoinType(parentEdge);
if(parentEdge != null) {
idList.put(queryTree.getQuery(), getParentIds(parentEdge));
}
String queryString = queryTree.getQuery().getQueryString();
if (joinType == InterQuery.JoinType.INLIST) {
Set inlistvalues = idList.get(queryTree.getQuery());
int size = inlistvalues.size() >= MAX_INLIST_SIZE ? MAX_INLIST_SIZE : inlistvalues.size();
// This is simple replace
queryString = queryString.replaceFirst(
Pattern.quote(Query.INTERQUERY_JOIN_PLACEHOLDER),
getParentInListBindString(size, queryTree.getQuery()));
}
else if(joinType == InterQuery.JoinType.JOINTABLE) {
// do nothing
// either INSERT the parent ids or
// INSERT using the parent query
// TODO: update the fact that the rows were inserted
} else {
queryString = queryString.replaceFirst(
Pattern.quote(Query.INTERQUERY_JOIN_PLACEHOLDER),
deriveSubquery(edge, edge.getStart().getQuery().getQueryString()));
}
queryTree.getQuery().setQueryString(queryString);
}
private String deriveSubquery(InterQuery edge, String oql) {
// We need to select only the parent id from the original parent oql
// So we first split the query around the FROM clause
// then prepend the select clause for the parent id
StringBuilder subquery = new StringBuilder("SELECT ");
subquery.append(edge.getSource().getId()).append(oql.substring(oql.indexOf(" FROM ")));
return subquery.toString();
}
public void initInList(Query query) {
if(idList.containsKey(query)) {
Set inlistvalues = idList.get(query);
if(inlistvalues.size() <= MAX_INLIST_SIZE) {
// Set the parameters for the IN list
int start = OFFSET;
Iterator iter = inlistvalues.iterator();
while (iter.hasNext()) {
query.setParameter(QueryFragment.PARENT_INLIST + start++, iter.next());
}
} else {
query.processLargeInList(inlistvalues);
}
}
}
private String getParentInListBindString(int count, Query query) {
StringBuilder result = new StringBuilder();
// We always give a name for the parameter since we are building the query
if(query.isOQL() || query.isSQL()) {
int i = OFFSET;
for(; i < count; i++) {
result.append(":").append(QueryFragment.PARENT_INLIST + i).append(", ");
}
result.append(":").append(QueryFragment.PARENT_INLIST + i);
} else {
throw new RuntimeException("Cannot call getParentInListBindString on a Stored Procedure");
}
return result.toString();
}
private InterQuery.JoinType getJoinType(InterQuery edge) {
InterQuery.JoinType joinType = InterQuery.JoinType.INLIST;
if(!parentIdList.containsKey(edge.getSource())) {
throw new RuntimeException("Child query can only be invoked after parent query has returned");
}
// TODO: Enable subquery/exists only in a restricted setup
// 1. child and all the ancestor queries are of the same type (e.g., SQL/OQL)
// 2. A new parent query needs to be built where we don't need to pull in additional information
// 3. The NativeQuery and OQLQuery objects need to a have a primarykey attribute to identify the primary key
// to be used for EXISTS correlation
//if(parentIdList.get(edge.getSource()).size() > MAX_INLIST_SIZE) {
// joinType = InterQuery.JoinType.SUBQUERY;
//}
QueryTree childQuery = edge.getEnd();
// first check that the child query is either
// 1. SQL
// 2. Stored procedure query
// and if (1) then check that it references the query join table
if(QueryJoinAction.needsQueryJoinTable(childQuery)) {
joinType = InterQuery.JoinType.JOINTABLE;
}
return joinType;
}
public static class QueryVisitor {
// This is a list because to support scrolling we need to
// know the last id
List ids = new LinkedList<>();
public void addId(Object id) {
if(id != null) {
ids.add(id);
}
}
}
public void visit(String path, Object value) {
if(visitorsByPath.containsKey(path)) {
visitorsByPath.get(path).addId(value);
}
}
public void start(AggregateTree> at, QueryTree qt) {
// Loop through each outgoing edge and create a visitor for them
for(InterQuery outgoing: at.getOutEdges(qt)) {
// Multiple InterQuery edges can emanate from the same QueryFragment source
if(!visitors.containsKey(outgoing.getSource())) {
QueryVisitor visitor = new QueryVisitor();
visitors.put(outgoing.getSource(), visitor);
visitorsByPath.put(outgoing.getSource().getIdField().getFullPath(), visitor);
}
}
}
public void finish(AggregateTree> at, QueryTree qt) {
// Loop through each outgoing edge and process the results
for(InterQuery outgoing: at.getOutEdges(qt)) {
// check if it has already been processed, since multiple InterQuery edges
// can start from the same QueryFragment source
QueryFragment source = outgoing.getSource();
if(visitors.containsKey(source)) {
QueryVisitor visitor = visitors.get(source);
parentIdList.put(source, new HashSet<>(visitor.ids));
if(visitor.ids.size() > 0) {
lastParentId.put(qt, visitor.ids.get(visitor.ids.size()-1));
}
// Now we no longer need this visitor as it has been processed
visitors.remove(source);
}
}
}
public void visit(String path, BusinessObject bo) {
path = QueryFragment.extractAnchorPath(path);
List bos = objectsByPath.get(path);
if(bos == null) {
bos = new LinkedList<>();
objectsByPath.put(path, bos);
}
bos.add(bo);
Object id = bo.getIdentifierValue();
// Currently we only support tracking objects with surrogate key
if (id != null) {
EntityKey key = new SurrogateEntityKey(id, AbstractTypeMapper.getSurrogateKeyTypeName(bo.getType()), path);
BusinessObject existing = queryObjects.get(key);
if (existing != bo) {
queryObjects.put(key, bo);
}
}
}
public BusinessObject getQueryObject(String path, Object idValue, Type type) {
EntityKey key = new SurrogateEntityKey(idValue, AbstractTypeMapper.getSurrogateKeyTypeName(type), path);
return queryObjects.get(key);
}
public void addRecordDelta(QueryTree queryTree, Set changed, Map propertyResult, String lcp, Object[] record) {
List deltas = recordDeltas.get(queryTree);
if(deltas == null) {
deltas = new LinkedList<>();
recordDeltas.put(queryTree, deltas);
}
deltas.add(new RecordDelta(changed, propertyResult, lcp, record));
}
public List getRecordDeltas(QueryTree queryTree) {
return this.recordDeltas.get(queryTree);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy