/**
* 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.rest.resource;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.structr.common.PropertyView;
import org.structr.common.SecurityContext;
import org.structr.common.error.FrameworkException;
import org.structr.core.GraphObjectMap;
import org.structr.core.Result;
import org.structr.core.app.StructrApp;
import org.structr.core.entity.AbstractNode;
import org.structr.core.entity.AbstractRelationship;
import org.structr.core.entity.Relation;
import org.structr.core.entity.Relation.Multiplicity;
import org.structr.core.entity.relationship.SchemaRelationship;
import org.structr.core.graph.search.SearchCommand;
import org.structr.core.property.BooleanProperty;
import org.structr.core.property.GenericProperty;
import org.structr.core.property.LongProperty;
import org.structr.core.property.PropertyKey;
import org.structr.core.property.RelationProperty;
import org.structr.core.property.StringProperty;
import org.structr.rest.RestMethodResult;
import org.structr.rest.exception.IllegalMethodException;
import org.structr.rest.exception.IllegalPathException;
import org.structr.schema.ConfigurationProvider;
import org.structr.schema.SchemaHelper;
/**
*
*
*/
public class SchemaResource extends Resource {
private static final Logger logger = LoggerFactory.getLogger(SchemaResource.class.getName());
private static final StringProperty urlProperty = new StringProperty("url");
private static final StringProperty typeProperty = new StringProperty("type");
private static final StringProperty nameProperty = new StringProperty("name");
private static final StringProperty classNameProperty = new StringProperty("className");
private static final StringProperty extendsClassNameProperty = new StringProperty("extendsClass");
private static final BooleanProperty isRelProperty = new BooleanProperty("isRel");
private static final LongProperty flagsProperty = new LongProperty("flags");
private static final GenericProperty relatedToProperty = new GenericProperty("relatedTo");
private static final GenericProperty relatedFromProperty = new GenericProperty("relatedFrom");
private static final GenericProperty possibleSourceTypesProperty = new GenericProperty("possibleSourceTypes");
private static final GenericProperty possibleTargetTypesProperty = new GenericProperty("possibleTargetTypes");
private static final BooleanProperty allSourceTypesPossibleProperty = new BooleanProperty("allSourceTypesPossible");
private static final BooleanProperty allTargetTypesPossibleProperty = new BooleanProperty("allTargetTypesPossible");
private static final BooleanProperty htmlSourceTypesPossibleProperty = new BooleanProperty("htmlSourceTypesPossible");
private static final BooleanProperty htmlTargetTypesPossibleProperty = new BooleanProperty("htmlTargetTypesPossible");
public enum UriPart {
_schema
}
@Override
public boolean checkAndConfigure(String part, SecurityContext securityContext, HttpServletRequest request) throws FrameworkException {
this.securityContext = securityContext;
if (UriPart._schema.name().equals(part)) {
return true;
}
return false;
}
@Override
public Result doGet(PropertyKey sortKey, boolean sortDescending, int pageSize, int page, String offsetId) throws FrameworkException {
return getSchemaOverviewResult();
}
@Override
public RestMethodResult doPost(Map<String, Object> propertySet) throws FrameworkException {
throw new IllegalMethodException("POST not allowed on " + getResourceSignature());
}
@Override
public Resource tryCombineWith(Resource next) throws FrameworkException {
if (next instanceof TypeResource) {
SchemaTypeResource schemaTypeResource = new SchemaTypeResource(securityContext, (TypeResource) next);
return schemaTypeResource;
}
if (next != null) {
logger.warn("Trying to combine SchemaResource with {}.", next.getClass().getName());
} else {
logger.warn("Trying to combine SchemaResource with null.");
}
throw new IllegalPathException("Illegal path, /" + getResourceSignature() + " must be followed by a type resource");
}
@Override
public String getUriPart() {
return "";
}
@Override
public Class getEntityClass() {
return null;
}
@Override
public String getResourceSignature() {
return UriPart._schema.name();
}
@Override
public boolean isCollectionResource() throws FrameworkException {
return true;
}
// ----- public static methods -----
public static Result getSchemaOverviewResult() throws FrameworkException {
final List<GraphObjectMap> resultList = new LinkedList<>();
final ConfigurationProvider config = StructrApp.getConfiguration();
// extract types from ModuleService
final Set<String> nodeEntityKeys = config.getNodeEntities().keySet();
final Set<String> relEntityKeys = config.getRelationshipEntities().keySet();
Set<String> entityKeys = new HashSet<>();
entityKeys.addAll(nodeEntityKeys);
entityKeys.addAll(relEntityKeys);
for (String rawType : entityKeys) {
// create & add schema information
Class type = SchemaHelper.getEntityClassForRawType(rawType);
GraphObjectMap schema = new GraphObjectMap();
resultList.add(schema);
if (type != null) {
String url = "/".concat(rawType);
final boolean isRel = AbstractRelationship.class.isAssignableFrom(type);
schema.setProperty(urlProperty, url);
schema.setProperty(typeProperty, type.getSimpleName());
schema.setProperty(nameProperty, type.getSimpleName());
schema.setProperty(classNameProperty, type.getName());
schema.setProperty(extendsClassNameProperty, type.getSuperclass().getName());
schema.setProperty(isRelProperty, isRel);
schema.setProperty(flagsProperty, SecurityContext.getResourceFlags(rawType));
if (!isRel) {
final List<GraphObjectMap> relatedTo = new LinkedList<>();
final List<GraphObjectMap> relatedFrom = new LinkedList<>();
for (final PropertyKey key : config.getPropertySet(type, PropertyView.All)) {
if (key instanceof RelationProperty) {
final RelationProperty relationProperty = (RelationProperty)key;
final Relation relation = relationProperty.getRelation();
if (!relation.isHidden()) {
switch (relation.getDirectionForType(type)) {
case OUTGOING:
relatedTo.add(relationPropertyToMap(config, relationProperty));
break;
case INCOMING:
relatedFrom.add(relationPropertyToMap(config, relationProperty));
break;
case BOTH:
relatedTo.add(relationPropertyToMap(config, relationProperty));
relatedFrom.add(relationPropertyToMap(config, relationProperty));
break;
}
}
}
}
if (!relatedTo.isEmpty()) {
schema.setProperty(relatedToProperty, relatedTo);
}
if (!relatedFrom.isEmpty()) {
schema.setProperty(relatedFromProperty, relatedFrom);
}
}
}
}
return new Result(resultList, resultList.size(), false, false);
}
// ----- private methods -----
private static GraphObjectMap relationPropertyToMap(final ConfigurationProvider config, final RelationProperty relationProperty) {
final GraphObjectMap map = new GraphObjectMap();
final Relation relation = relationProperty.getRelation();
/**
* what we need here:
* id,
* sourceMultiplicity,
* targetMultiplicity,
* relationshipType,
*
*/
map.put(SchemaRelationship.sourceMultiplicity, multiplictyToString(relation.getSourceMultiplicity()));
map.put(SchemaRelationship.targetMultiplicity, multiplictyToString(relation.getTargetMultiplicity()));
map.put(typeProperty, relation.getClass().getSimpleName());
map.put(SchemaRelationship.relationshipType, relation.name());
final Class sourceType = relation.getSourceType();
final Class targetType = relation.getTargetType();
// select AbstractNode and SUPERCLASSES (not subclasses!)
if (sourceType.isAssignableFrom(AbstractNode.class)) {
map.put(allSourceTypesPossibleProperty, true);
map.put(htmlSourceTypesPossibleProperty, true);
map.put(possibleSourceTypesProperty, null);
} else if ("DOMNode".equals(sourceType.getSimpleName())) {
map.put(allTargetTypesPossibleProperty, false);
map.put(htmlTargetTypesPossibleProperty, true);
map.put(possibleTargetTypesProperty, null);
} else {
map.put(allSourceTypesPossibleProperty, false);
map.put(htmlSourceTypesPossibleProperty, false);
map.put(possibleSourceTypesProperty, StringUtils.join(SearchCommand.getAllSubtypesAsStringSet(sourceType.getSimpleName()), ","));
}
// select AbstractNode and SUPERCLASSES (not subclasses!)
if (targetType.isAssignableFrom(AbstractNode.class)) {
map.put(allTargetTypesPossibleProperty, true);
map.put(htmlTargetTypesPossibleProperty, true);
map.put(possibleTargetTypesProperty, null);
} else if ("DOMNode".equals(targetType.getSimpleName())) {
map.put(allTargetTypesPossibleProperty, false);
map.put(htmlTargetTypesPossibleProperty, true);
map.put(possibleTargetTypesProperty, null);
} else {
map.put(allTargetTypesPossibleProperty, false);
map.put(htmlTargetTypesPossibleProperty, false);
map.put(possibleTargetTypesProperty, StringUtils.join(SearchCommand.getAllSubtypesAsStringSet(targetType.getSimpleName()), ","));
}
return map;
}
private static String multiplictyToString(final Multiplicity multiplicity) {
switch (multiplicity) {
case One:
return "1";
case Many:
return "*";
}
return null;
}
}