/** * Copyright (C) 2010-2017 Structr GmbH * * This file is part of Structr <http://structr.org>. * * Structr is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * Structr is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Structr. If not, see <http://www.gnu.org/licenses/>. */ package org.structr.core.notion; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.structr.common.EntityAndPropertiesContainer; import org.structr.common.SecurityContext; import org.structr.common.error.FrameworkException; import org.structr.common.error.TypeToken; import org.structr.core.GraphObject; import org.structr.core.app.App; import org.structr.core.app.StructrApp; import org.structr.core.entity.Principal; import org.structr.core.entity.Relation; import org.structr.core.graph.NodeInterface; import org.structr.core.graph.search.SearchCommand; import org.structr.core.property.PropertyKey; import org.structr.core.property.PropertyMap; import org.structr.core.property.RelationProperty; /** * Deserializes a {@link GraphObject} using the UUID property. * * */ public class IdDeserializationStrategy<S, T extends NodeInterface> implements DeserializationStrategy<S, T> { private static final Logger logger = LoggerFactory.getLogger(IdDeserializationStrategy.class.getName()); protected RelationProperty<S> relationProperty = null; public IdDeserializationStrategy() { } @Override public void setRelationProperty(final RelationProperty<S> parentProperty) { this.relationProperty = parentProperty; } @Override public T deserialize(final SecurityContext securityContext, final Class<T> type, final S source, final Object context) throws FrameworkException { final App app = StructrApp.getInstance(securityContext); if (source != null) { if (source instanceof Map) { final Map<String, Object> properties = (Map<String, Object>) source; Class<T> actualType = type; if (actualType != null && actualType.isInterface()) { // try to identify concrete type from input set // (creation wouldn't work otherwise anyway) if (properties.containsKey(NodeInterface.type.jsonName())) { final String typeFromInput = properties.get(NodeInterface.type.jsonName()).toString(); actualType = StructrApp.getConfiguration().getNodeEntityClass(typeFromInput); // reset type on failed check if (actualType == null) { actualType = type; } } } final PropertyMap convertedProperties = PropertyMap.inputTypeToJavaType(securityContext, actualType, properties); final Set<PropertyKey> allProperties = StructrApp.getConfiguration().getPropertySet(type, "all"); final Map<String, Object> foreignProps = new HashMap<>(); T relatedNode = null; // If property map contains the uuid, search only for uuid if (convertedProperties.containsKey(GraphObject.id)) { relatedNode = (T) app.getNodeById(convertedProperties.get(GraphObject.id)); if (relatedNode != null) { if ( !SearchCommand.isTypeAssignableFromOtherType(type, relatedNode.getClass()) ) { throw new FrameworkException(422, "Node type mismatch", new TypeToken(type.getSimpleName(), null, type.getSimpleName())); } for (final PropertyKey key : convertedProperties.keySet()) { if (!key.isUnique() && !isIdentifying(actualType, key) && !allProperties.contains(key)) { // store "foreign" properties (those that are to be set on the newly created relationship foreignProps.put(key.jsonName(), properties.get(key.jsonName())); } } // node found, remove UUID convertedProperties.remove(GraphObject.id); } } else { final PropertyMap uniqueKeyValues = new PropertyMap(); for (final PropertyKey key : convertedProperties.keySet()) { if (key.isUnique() || isIdentifying(actualType, key)) { uniqueKeyValues.put(key, convertedProperties.get(key)); } else if (!allProperties.contains(key)) { // store "foreign" properties (those that are to be set on the newly created relationship foreignProps.put(key.jsonName(), properties.get(key.jsonName())); } } // try to find an entity for the given attributes, but only if they are unique // (this is quite similar to the Cypher MERGE command), if (!uniqueKeyValues.isEmpty()) { final List<T> possibleResults = app.nodeQuery(type).and(uniqueKeyValues).getAsList(); final int num = possibleResults.size(); switch (num) { case 0: // not found => will be created break; case 1: relatedNode = possibleResults.get(0); break; default: // more than one => not unique?? throw new FrameworkException(422, concat( "Unable to resolve related node of type ", type.getSimpleName(), ", ambiguous result: found ", num, " nodes for the given property set." )); } } else { // throw exception here? } } if (relatedNode == null) { // no related node found, should we create one? if (relationProperty != null) { final Relation relation = relationProperty.getRelation(); if (relationProperty.doAutocreate()) { return app.create(type, convertedProperties); } else { throw new FrameworkException(422, concat( "Cannot create ", relation.getOtherType(type).getSimpleName(), ": no matching ", type.getSimpleName(), " found for the given property set ", convertedProperties, " and autoCreate has a value of ", relationProperty.getAutocreateFlagName() )); } } // FIXME: when can the relationProperty be null at all? throw new FrameworkException(500, concat( "Unable to resolve related node of type ", type.getSimpleName(), ", no relation defined." )); } else { // set properties on related node? if (!convertedProperties.isEmpty()) { setProperties(securityContext, relatedNode, convertedProperties); } if (foreignProps.isEmpty()) { return relatedNode; } else { return (T)new EntityAndPropertiesContainer(relatedNode, foreignProps); } } } else if (type.isAssignableFrom(source.getClass())) { return (T)source; } else { // interpret source as a raw ID string and fetch entity final GraphObject obj = app.getNodeById(source.toString()); if (obj != null && !type.isAssignableFrom(obj.getClass())) { throw new FrameworkException(422, "Node type mismatch", new TypeToken(obj.getClass().getSimpleName(), null, type.getSimpleName())); } return (T) obj; } } return null; } private String concat(final Object... values) { final StringBuilder buf = new StringBuilder(values.length * 20); for (Object value : values) { buf.append(value); } return buf.toString(); } private boolean isIdentifying(final Class actualType, final PropertyKey key) { return (Principal.class.isAssignableFrom(actualType) && (Principal.name.equals(key) || Principal.eMail.equals(key))); } }