Package org.structr.rest.resource

Source Code of org.structr.rest.resource.StaticRelationshipResource

/**
* Copyright (C) 2010-2014 Morgner UG (haftungsbeschränkt)
*
* 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.InvocationTargetException;
import org.structr.common.PagingHelper;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.neo4j.graphdb.Node;
import org.neo4j.helpers.Predicate;
import org.neo4j.helpers.collection.Iterables;

import org.structr.core.property.PropertyKey;
import org.structr.common.SecurityContext;
import org.structr.common.error.FrameworkException;
import org.structr.core.Export;
import org.structr.core.GraphObject;
import org.structr.core.Result;
import org.structr.core.app.App;
import org.structr.core.app.Query;
import org.structr.core.app.StructrApp;
import org.structr.core.entity.AbstractNode;
import org.structr.core.entity.OtherNodeTypeRelationFilter;
import org.structr.core.entity.Relation;
import org.structr.core.graph.NodeFactory;
import org.structr.core.graph.NodeInterface;
import org.structr.core.notion.Notion;
import org.structr.core.property.RelationProperty;
import org.structr.rest.RestMethodResult;
import org.structr.rest.exception.IllegalPathException;
//~--- JDK imports ------------------------------------------------------------

//~--- classes ----------------------------------------------------------------
/**
*
* @author Christian Morgner
*/
public class StaticRelationshipResource extends SortableResource {

  private static final Logger logger = Logger.getLogger(StaticRelationshipResource.class.getName());

  //~--- fields ---------------------------------------------------------
  TypeResource typeResource = null;
  TypedIdResource typedIdResource = null;
  PropertyKey propertyKey = null;

  //~--- constructors ---------------------------------------------------constructors
  public StaticRelationshipResource(final SecurityContext securityContext, final TypedIdResource typedIdResource, final TypeResource typeResource) {

    this.securityContext = securityContext;
    this.typedIdResource = typedIdResource;
    this.typeResource = typeResource;
    this.propertyKey = findPropertyKey(typedIdResource, typeResource);
  }

  //~--- methods --------------------------------------------------------
  @Override
  public Result doGet(final PropertyKey sortKey, final boolean sortDescending, final int pageSize, final int page, final String offsetId) throws FrameworkException {

    // ok, source node exists, fetch it
    final GraphObject sourceEntity = typedIdResource.getEntity();
    if (sourceEntity != null) {

      // first try: look through existing relations
      if (propertyKey == null) {

        if (sourceEntity instanceof NodeInterface) {

          if (!typeResource.isNode) {

            final NodeInterface source = (NodeInterface) sourceEntity;
            final Node sourceNode = source.getNode();
            final Class relationshipType = typeResource.entityClass;
            final Relation relation = AbstractNode.getRelationshipForType(relationshipType);
            final Class destNodeType = relation.getOtherType(typedIdResource.getEntityClass());
            final Set partialResult = new LinkedHashSet<>(typeResource.doGet(sortKey, sortDescending, NodeFactory.DEFAULT_PAGE_SIZE, NodeFactory.DEFAULT_PAGE, null).getResults());

            // filter list according to end node type
            final Set<GraphObject> set = Iterables.toSet(Iterables.filter(new OtherNodeTypeRelationFilter(securityContext, sourceNode, destNodeType), source.getRelationships(relationshipType)));

            // intersect partial result with result list
            set.retainAll(partialResult);

            final List<GraphObject> finalResult = new LinkedList<>(set);

            // sort after merge
            applyDefaultSorting(finalResult, sortKey, sortDescending);

            // return result
            return new Result(PagingHelper.subList(finalResult, pageSize, page, offsetId), finalResult.size(), isCollectionResource(), isPrimitiveArray());

          } else {

            // what here?
          }
        }

      } else {
//
//      // second try: property key
//      if (propertyKey != null) {

        Query query = typeResource.query;
        if (query == null) {

          query = StructrApp.getInstance(securityContext).nodeQuery();
        }

        // use search context from type resource
        typeResource.collectSearchAttributes(query);

        final Predicate<GraphObject> predicate = query.toPredicate();
        final Object value = sourceEntity.getProperty(propertyKey, predicate);

        if (value != null) {

          if (value instanceof Iterable) {

            final Set<GraphObject> propertyResults = new LinkedHashSet<>();

            // fill set with data
            for (final GraphObject obj : ((Iterable<GraphObject>) value)) {
              propertyResults.add(obj);
            }

            /*
             if (typeResource.getEntityClass() != null) {

             final Set<GraphObject> typeResourceResults = new LinkedHashSet<>(typeResource.doGet(null, sortDescending, NodeFactory.DEFAULT_PAGE_SIZE, NodeFactory.DEFAULT_PAGE, null).getResults());

             // merge list with results from type resource (which includes request parameter based filtering)
             propertyResults.retainAll(typeResourceResults);
             }
             */
            final List<GraphObject> finalResult = new LinkedList<>(propertyResults);
            applyDefaultSorting(finalResult, sortKey, sortDescending);

            // return result
            return new Result(PagingHelper.subList(finalResult, pageSize, page, offsetId), finalResult.size(), isCollectionResource(), isPrimitiveArray());
          }
        }

      }
    }

    return Result.EMPTY_RESULT;
  }

  @Override
  public RestMethodResult doPut(final Map<String, Object> propertySet) throws FrameworkException {

    final List<? extends GraphObject> results = typedIdResource.doGet(null, false, NodeFactory.DEFAULT_PAGE_SIZE, NodeFactory.DEFAULT_PAGE, null).getResults();
    final App app = StructrApp.getInstance(securityContext);

    if (results != null) {

      // fetch static relationship definition
      if (propertyKey != null && propertyKey instanceof RelationProperty) {

        final GraphObject sourceEntity = typedIdResource.getEntity();
        if (sourceEntity != null) {

          if (propertyKey.isReadOnly()) {

            logger.log(Level.INFO, "Read-only property on {1}: {0}", new Object[]{sourceEntity.getClass(), typeResource.getRawType()});
            return new RestMethodResult(HttpServletResponse.SC_FORBIDDEN);

          }

          final List<GraphObject> nodes = new LinkedList<>();

          // Now add new relationships for any new id: This should be the rest of the property set
          for (final Object obj : propertySet.values()) {

            nodes.add(app.get(obj.toString()));
          }

          // set property on source node
          sourceEntity.setProperty(propertyKey, nodes);
        }

      }
    }

    return new RestMethodResult(HttpServletResponse.SC_OK);
  }

  @Override
  public RestMethodResult doPost(final Map<String, Object> propertySet) throws FrameworkException {

    final GraphObject sourceNode = typedIdResource.getEntity();

    if (sourceNode != null && propertyKey != null && propertyKey instanceof RelationProperty) {

      final RelationProperty relationProperty = (RelationProperty) propertyKey;
      final Class sourceNodeType = sourceNode.getClass();
      NodeInterface newNode = null;

      if (propertyKey.isReadOnly()) {

        logger.log(Level.INFO, "Read-only property on {0}: {1}", new Object[]{sourceNodeType, typeResource.getRawType()});

        return null;
      }

      // fetch notion
      final Notion notion = relationProperty.getNotion();
      final PropertyKey primaryPropertyKey = notion.getPrimaryPropertyKey();

      // apply notion if the property set contains the ID property as the only element
      if (primaryPropertyKey != null && propertySet.containsKey(primaryPropertyKey.jsonName()) && propertySet.size() == 1) {

        /*
         * FIXME: is this needed at all??
         *
         // the notion that is defined for this relationship can deserialize
         // objects with a single key (uuid for example), and the POSTed
         // property set contains value(s) for this key, so we only need
         // to create relationships
         final Object keySource = propertySet.get(primaryPropertyKey.jsonName());
         if (keySource != null) {

         if (keySource instanceof Collection) {

         sourceNode.setProperty(propertyKey, notion.getCollectionAdapterForSetter(securityContext).adapt(keySource));

         } else {

         sourceNode.setProperty(propertyKey, notion.getAdapterForSetter(securityContext).adapt(keySource));
         }

         /*
         GraphObject otherNode = null;

         if (keySource instanceof Collection) {

         final Collection collection = (Collection) keySource;

         for (final Object key : collection) {

         otherNode = deserializationStrategy.adapt(key);

         if (otherNode != null && otherNode instanceof AbstractNode) {

         relationshipProperty.createRelationship(securityContext, sourceNode, (AbstractNode)otherNode);

         } else {

         logger.log(Level.WARNING, "Relationship end node has invalid type {0}", otherNode.getClass().getName());
         }

         }

         } else {

         // create a single relationship
         otherNode = deserializationStrategy.adapt(keySource);

         if (otherNode != null && otherNode instanceof AbstractNode) {

         relationshipProperty.createRelationship(securityContext, sourceNode, (AbstractNode)otherNode);

         } else {

         logger.log(Level.WARNING, "Relationship end node has invalid type {0}", otherNode.getClass().getName());

         }
         }

         return otherNode;

         } else {

         logger.log(Level.INFO, "Key {0} not found in {1}", new Object[] { primaryPropertyKey.jsonName(), propertySet.toString() });

         }
         */
      } else {

        // the notion can not deserialize objects with a single key, or the POSTed propertySet did not contain a key to deserialize,
        // so we create a new node from the POSTed properties and link the source node to it. (this is the "old" implementation)
        newNode = typeResource.createNode(propertySet);
        if (newNode != null) {

          relationProperty.addSingleElement(securityContext, sourceNode, newNode);
        }
      }

      if (newNode != null) {

        RestMethodResult result = new RestMethodResult(HttpServletResponse.SC_CREATED);
        result.addHeader("Location", buildLocationHeader(newNode));

        return result;
      }

    } else {

      // look for methods that have an @Export annotation
      GraphObject entity = typedIdResource.getIdResource().getEntity();
      Class entityType = typedIdResource.getEntityClass();
      String methodName = typeResource.getRawType();

      if (entity != null && entityType != null && methodName != null) {

        for (Method method : StructrApp.getConfiguration().getExportedMethodsForType(entityType)) {

          if (methodName.equals(method.getName())) {

            if (method.getAnnotation(Export.class) != null) {

              try {
                Object[] parameters = extractParameters(propertySet, method.getParameterTypes());
                return (RestMethodResult) method.invoke(entity, parameters);

              } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException t) {

                if (t instanceof FrameworkException) {

                  throw (FrameworkException) t;

                } else if (t.getCause() instanceof FrameworkException) {

                  throw (FrameworkException) t.getCause();

                } else {

                  logger.log(Level.WARNING, "Unable to call RPC method {0}: {1}", new Object[]{methodName, t.getMessage()});
                }
              }
            }
          }
        }
      }

    }

    throw new IllegalPathException();
  }

  @Override
  public boolean checkAndConfigure(final String part, final SecurityContext securityContext, final HttpServletRequest request) {
    return false;
  }

  @Override
  public Resource tryCombineWith(final Resource next) throws FrameworkException {

    if (next instanceof TypeResource) {

      throw new IllegalPathException();

    }

    return super.tryCombineWith(next);
  }

  @Override
  public Class getEntityClass() {
    Class type = typeResource.getEntityClass();
    if (type == null && propertyKey != null) {
      return propertyKey.relatedType();
    }
    return type;
  }

  @Override
  public String getUriPart() {
    return typedIdResource.getUriPart().concat("/").concat(typeResource.getUriPart());
  }

  public TypedIdResource getTypedIdConstraint() {
    return typedIdResource;
  }

  public TypeResource getTypeConstraint() {
    return typeResource;
  }

  @Override
  public boolean isCollectionResource() {
    return true;
  }

  @Override
  public String getResourceSignature() {
    return typedIdResource.getResourceSignature().concat("/").concat(typeResource.getResourceSignature());
  }

  // ----- private methods -----
  private <A extends NodeInterface, B extends NodeInterface, R extends Relation<A, B, ?, ?>> R getRelationshipForType(final Class<R> type) {

    try {

      return type.newInstance();

    } catch (Throwable t) {

      // TODO: throw meaningful exception here,
      // should be a RuntimeException that indicates
      // wrong use of Relationships etc.
      t.printStackTrace();
    }

    return null;
  }

  private Object[] extractParameters(Map<String, Object> properties, Class[] parameterTypes) {

    List<Object> values = new ArrayList<>(properties.values());
    List<Object> parameters = new ArrayList<>();
    int index = 0;

    // only try to convert when both lists have equal size
    if (values.size() == parameterTypes.length) {

      for (Class parameterType : parameterTypes) {

        Object value = convert(values.get(index++), parameterType);
        if (value != null) {

          parameters.add(value);
        }
      }
    }

    return parameters.toArray(new Object[0]);
  }

  /*
   * Tries to convert the given value into an object
   * of the given type, using an intermediate type
   * of String for the conversion.
   */
  private Object convert(Object value, Class type) {

    Object convertedObject = null;

    if (type.equals(String.class)) {

      // strings can be returned immediately
      return value.toString();

    } else if (value instanceof Number) {

      Number number = (Number) value;

      if (type.equals(Integer.class) || type.equals(Integer.TYPE)) {
        return number.intValue();

      } else if (type.equals(Long.class) || type.equals(Long.TYPE)) {
        return number.longValue();

      } else if (type.equals(Double.class) || type.equals(Double.TYPE)) {
        return number.doubleValue();

      } else if (type.equals(Float.class) || type.equals(Float.TYPE)) {
        return number.floatValue();

      } else if (type.equals(Short.class) || type.equals(Integer.TYPE)) {
        return number.shortValue();

      } else if (type.equals(Byte.class) || type.equals(Byte.TYPE)) {
        return number.byteValue();

      }

    } else if (value instanceof List) {

      return value;

    } else if (value instanceof Map) {
      return value;
    }

    // fallback
    try {

      Method valueOf = type.getMethod("valueOf", String.class);
      if (valueOf != null) {

        convertedObject = valueOf.invoke(null, value.toString());

      } else {

        logger.log(Level.WARNING, "Unable to find static valueOf method for type {0}", type);
      }

    } catch (Throwable t) {

      logger.log(Level.WARNING, "Unable to deserialize value {0} of type {1}, Class has no static valueOf method.", new Object[]{value, type});
    }

    return convertedObject;
  }
}
TOP

Related Classes of org.structr.rest.resource.StaticRelationshipResource

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.