/** * 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.lang.reflect.Method; import java.util.Collection; import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.mozilla.javascript.Context; import org.mozilla.javascript.ScriptRuntime; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.structr.common.SecurityContext; import org.structr.common.error.FrameworkException; import org.structr.core.GraphObject; import org.structr.core.Result; import org.structr.core.app.App; import org.structr.core.app.StructrApp; import org.structr.core.entity.SchemaMethod; import org.structr.core.entity.SchemaNode; import org.structr.core.graph.Tx; import org.structr.core.property.PropertyKey; import org.structr.rest.RestMethodResult; import org.structr.rest.exception.IllegalMethodException; import org.structr.rest.exception.IllegalPathException; import org.structr.schema.action.Actions; /** * */ public class SchemaMethodResource extends SortableResource { private static final Logger logger = LoggerFactory.getLogger(SchemaMethodResource.class); private TypeResource typeResource = null; private TypeResource methodResource = null; private String source = null; public SchemaMethodResource(final SecurityContext securityContext, final TypeResource typeResource, final TypeResource methodResource) throws IllegalPathException { this.typeResource = typeResource; this.methodResource = methodResource; this.securityContext = securityContext; this.source = findMethodSource(typeResource.getEntityClass(), methodResource.getRawType()); } @Override public boolean checkAndConfigure(final String part, final SecurityContext securityContext, final HttpServletRequest request) throws FrameworkException { // never return this method in a URL path evaluation return false; } @Override public String getResourceSignature() { return typeResource.getResourceSignature() + "/" + methodResource.getResourceSignature(); } @Override public Result doGet(final PropertyKey sortKey, final boolean sortDescending, final int pageSize, final int page, final String offsetId) throws FrameworkException { throw new IllegalMethodException("GET not allowed on " + getResourceSignature()); } @Override public RestMethodResult doPost(final Map<String, Object> propertySet) throws FrameworkException { final App app = StructrApp.getInstance(securityContext); RestMethodResult result = null; if (source != null) { try (final Tx tx = app.tx()) { result = SchemaMethodResource.invoke(securityContext, null, source, propertySet, methodResource.getUriPart()); tx.success(); } } if (result == null) { throw new IllegalPathException("Type and method name do not match the given path."); } return result; } @Override public RestMethodResult doPut(final Map<String, Object> propertySet) throws FrameworkException { throw new IllegalMethodException("PUT not allowed on " + getResourceSignature()); } // ----- private methods ----- public static RestMethodResult invoke(final SecurityContext securityContext, final GraphObject entity, final String source, final Map<String, Object> propertySet, final String methodName) throws FrameworkException { return SchemaMethodResource.wrapInResult(Actions.execute(securityContext, entity, "${" + source.trim() + "}", propertySet, methodName)); } public static RestMethodResult wrapInResult(final Object obj) { RestMethodResult result = null; if (obj instanceof RestMethodResult) { result = (RestMethodResult)obj; } else { result = new RestMethodResult(200); // unwrap nested object(s) SchemaMethodResource.unwrapTo(obj, result); } return result; } public static String findMethodSource(final Class type, final String methodName) throws IllegalPathException { try { final App app = StructrApp.getInstance(); final String typeName = type.getSimpleName(); Class currentType = type; // first step: schema node or one of its parents SchemaNode schemaNode = app.nodeQuery(SchemaNode.class).andName(typeName).getFirst(); while (schemaNode != null) { for (final SchemaMethod method : schemaNode.getProperty(SchemaNode.schemaMethods)) { if (methodName.equals(method.getName())) { return method.getProperty(SchemaMethod.source); } } currentType = currentType.getSuperclass(); if (currentType != null) { // skip non-dynamic types if (currentType.getSimpleName().equals(typeName) || !currentType.getName().startsWith("org.structr.dynamic.")) { currentType = currentType.getSuperclass(); } if (currentType != null && currentType.getName().startsWith("org.structr.dynamic.")) { schemaNode = app.nodeQuery(SchemaNode.class).andName(currentType.getSimpleName()).getFirst(); } else { break; } } else { break; } } } catch (FrameworkException fex) {} throw new IllegalPathException("Type and method name do not match the given path."); } public static void unwrapTo(final Object source, final RestMethodResult result) { if (source != null) { final Object unwrapped = Context.jsToJava(source, ScriptRuntime.ObjectClass); if (unwrapped.getClass().isArray()) { for (final Object element : (Object[])unwrapped) { unwrapTo(element, result); } } else if (unwrapped instanceof Collection) { for (final Object element : (Collection)unwrapped) { unwrapTo(element, result); } } else if (unwrapped instanceof GraphObject) { result.addContent((GraphObject)unwrapped); } else { // cannot unwrap, pass literal result.setNonGraphObjectResult(unwrapped); } } } private Method determineMethod(final Class type, final String methodName) throws IllegalPathException { Method result = null; try { result = type.getMethod(methodName, Map.class); } catch (NoSuchMethodException ignore) { // fallback: check exported schema methods final Map<String, Method> methods = StructrApp.getConfiguration().getExportedMethodsForType(type); result = methods.get(methodName); } return result; } }