/* Copyright [2013-2014] eBay Software Foundation 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. */ /* Copyright 2012 eBay Software Foundation Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package com.ebay.cloud.cms.query.executor.result; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.codehaus.jackson.JsonNode; import com.ebay.cloud.cms.dal.entity.EntityMapper; import com.ebay.cloud.cms.dal.entity.IEntity; import com.ebay.cloud.cms.dal.entity.IEntityVisitor; import com.ebay.cloud.cms.dal.entity.JsonEntity; import com.ebay.cloud.cms.dal.entity.datahandler.IDataTypeHandler; import com.ebay.cloud.cms.dal.entity.json.datahandler.JsonDataTypeHandlerFactory; import com.ebay.cloud.cms.metadata.model.InternalFieldEnum; import com.ebay.cloud.cms.metadata.model.MetaAttribute; import com.ebay.cloud.cms.metadata.model.MetaField; import com.ebay.cloud.cms.metadata.model.MetaField.FieldProperty; import com.ebay.cloud.cms.metadata.model.MetaRelationship; import com.ebay.cloud.cms.metadata.model.MetaRelationship.RelationTypeEnum; import com.ebay.cloud.cms.query.metadata.AggregateMetaAttribute; import com.ebay.cloud.cms.query.parser.ParseQueryNode; public class QueryEntityVisitor implements IEntityVisitor { // stack data private ParseQueryNode parseNode; private QueryResult queryResult; private JsonEntity jsonEntity; // a map of json entity based on traverse level to avoid duplicate json enitty private Map<String, JsonEntity> resultEntityMap; private boolean hasDuplicatePath; public QueryEntityVisitor(ParseQueryNode parseNode, QueryResult queryResult) { this.parseNode = parseNode; this.queryResult = queryResult; this.resultEntityMap = new HashMap<String, JsonEntity>(); this.hasDuplicatePath = false; } @Override public Collection<String> getVisitFields(IEntity currentEntity) { if (parseNode.isRootDisplay()) { jsonEntity = resultEntityMap.get(currentEntity.getId()); if (jsonEntity == null) { jsonEntity = new JsonEntity(currentEntity.getMetaClass()); queryResult.addEntity(jsonEntity); addToResultMap(currentEntity.getId(), jsonEntity); } } return parseNode.getProjectionFields(); } @Override public void processAttribute(IEntity currentEntity, MetaField metaField) { if (jsonEntity == null) { return; } String fieldName = metaField.getName(); if (metaField instanceof AggregateMetaAttribute) { ((AggregateMetaAttribute) metaField).setAggregateFieldValue(currentEntity, jsonEntity); return; } if (currentEntity.hasField(fieldName)) { List<?> values = currentEntity.getFieldValues(fieldName); if (values.isEmpty()) { if (parseNode != null && parseNode.getFieldProjectSet().contains(fieldName)) { jsonEntity.setFieldValues(fieldName, Collections.EMPTY_LIST); } } else { for (Object val : values) { jsonEntity.addFieldValue(fieldName, val); } } } processFieldProperty(currentEntity, metaField); } private void processFieldProperty(IEntity currentEntity, MetaField metaField) { if (parseNode == null) { return; } IEntity bsonEntity = (IEntity) currentEntity; Map<MetaField, Map<FieldProperty, MetaAttribute>> projectFields = parseNode.getFieldPropProjectMap(); if (projectFields.containsKey(metaField)) { // set field property value to the json entity according to the faked name for (FieldProperty property : projectFields.get(metaField).keySet()) { MetaAttribute fakeAttribute = projectFields.get(metaField).get(property); Object value = bsonEntity.getFieldProperty(metaField.getName(), property.getName()); if (value != null) { // covert value using data type handler IDataTypeHandler handler = JsonDataTypeHandlerFactory.getHandler(property.getType()); JsonNode valueNode = (JsonNode) handler.write(currentEntity, value, fakeAttribute); jsonEntity.getNode().put(fakeAttribute.getName(), valueNode); } } } } @Override @SuppressWarnings("unchecked") public void processReference(IEntity currentEntity, MetaRelationship metaRelationship) { // backtrack // back stack state JsonEntity oldJsonEntity = jsonEntity; ParseQueryNode oldParseNode = parseNode; Map<String, JsonEntity> oldResultMap = resultEntityMap; boolean oldHasDup = hasDuplicatePath; resultEntityMap = new HashMap<String, JsonEntity>(); List<ParseQueryNode> parseNodes = parseNode.getNextQueryNode(metaRelationship); boolean isProjected = parseNode.getFieldProjectSet().contains(metaRelationship.getName()); if (parseNodes == null) { parseNode = null; processReferenceDetail(currentEntity, metaRelationship, isProjected); } else { hasDuplicatePath = hasDuplicatePath || parseNodes.size() > 1; // build existing json entity map to avoid duplication in A.(b && b) case if (oldJsonEntity != null && hasDuplicatePath) { List<JsonEntity> existingEntityList = (List<JsonEntity>)oldJsonEntity.getFieldValues(metaRelationship.getName()); for (JsonEntity existingEntity : existingEntityList) { addToResultMap(existingEntity.getId(), existingEntity); } } for (ParseQueryNode node : parseNodes) { parseNode = node; processReferenceDetail(currentEntity, metaRelationship, isProjected); } } // restore stack state jsonEntity = oldJsonEntity; parseNode = oldParseNode; resultEntityMap = oldResultMap; hasDuplicatePath = oldHasDup; processFieldProperty(currentEntity, metaRelationship); } @SuppressWarnings("unchecked") private void processReferenceDetail(IEntity currentEntity, MetaRelationship metaRelationship, boolean isProjected) { // backtrack JsonEntity oldJsonEntity = jsonEntity; ParseQueryNode oldParseNode = parseNode; String referenceName = metaRelationship.getName(); if (currentEntity.hasField(referenceName)) { List<IEntity> nextEntities = (List<IEntity>)currentEntity.getFieldValues(referenceName); if (nextEntities.isEmpty()) { if (isProjected && jsonEntity != null) { jsonEntity.setFieldValues(referenceName, Collections.EMPTY_LIST); } } else { boolean isEmbed = metaRelationship.getRelationType() == RelationTypeEnum.Embedded; for (IEntity nextEntity : nextEntities) { if (parseNode != null && parseNode.hasProjection()) { visitQueryNode(nextEntity, oldJsonEntity, referenceName); } else if (oldJsonEntity != null) { if (isEmbed) { jsonEntity = visitEmbedEntity(nextEntity, oldJsonEntity, referenceName); } else { jsonEntity = visitReference(nextEntity, oldJsonEntity, referenceName); } } } } } jsonEntity = oldJsonEntity; parseNode = oldParseNode; } private void visitQueryNode(IEntity nextEntity, JsonEntity oldJsonEntity, String referenceName) { JsonEntity jsonEntity = null; if (oldJsonEntity != null) { jsonEntity = resultEntityMap.get(nextEntity.getId()); if (jsonEntity == null) { jsonEntity = new JsonEntity(nextEntity.getMetaClass()); addToResultMap(nextEntity.getId(), jsonEntity); oldJsonEntity.addFieldValue(referenceName, jsonEntity); } this.jsonEntity = jsonEntity; } nextEntity.traverse(this); } private JsonEntity visitEmbedEntity(IEntity relationEntity, JsonEntity oldJsonEntity, String referenceName) { JsonEntity jsonEntity = resultEntityMap.get(relationEntity.getId()); if (jsonEntity == null) { EntityMapper mapper = new EntityMapper(JsonEntity.class, relationEntity.getMetaClass()); relationEntity.traverse(mapper); jsonEntity = (JsonEntity)mapper.getBuildEntity(); oldJsonEntity.addFieldValue(referenceName, jsonEntity); } addToResultMap(relationEntity.getId(), jsonEntity); return jsonEntity; } private void addToResultMap(String id, JsonEntity jsonEntity) { // aggregation might not have id if (id != null && hasDuplicatePath) { resultEntityMap.put(id, jsonEntity); } } private JsonEntity visitReference(IEntity relationEntity, JsonEntity oldJsonEntity, String referenceName) { JsonEntity jsonEntity = resultEntityMap.get(relationEntity.getId()); if (jsonEntity == null) { jsonEntity = new JsonEntity(relationEntity.getMetaClass()); // replace with reference only entity jsonEntity.setId(relationEntity.getId()); jsonEntity.addFieldValue(InternalFieldEnum.TYPE.getName(), relationEntity.getType()); oldJsonEntity.addFieldValue(referenceName, jsonEntity); } addToResultMap(relationEntity.getId(), jsonEntity); return jsonEntity; } }