Package org.exquery.restxq.impl

Source Code of org.exquery.restxq.impl.AbstractRestXqService

/**
* Copyright © 2012, Adam Retter / EXQuery
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*     * Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*     * Neither the name of the <organization> nor the
*       names of its contributors may be used to endorse or promote products
*       derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.exquery.restxq.impl;

import java.util.EnumSet;
import java.util.HashSet;
import java.util.Map.Entry;
import java.util.Set;
import org.exquery.http.AcceptHeader;
import org.exquery.http.AcceptHeader.Accept;
import org.exquery.http.HttpMethod;
import org.exquery.http.HttpRequest;
import org.exquery.http.HttpResponse;
import org.exquery.restxq.ResourceFunction;
import org.exquery.restxq.ResourceFunctionExecuter;
import org.exquery.restxq.RestXqService;
import org.exquery.restxq.RestXqServiceException;
import org.exquery.restxq.RestXqServiceSerializer;
import org.exquery.restxq.annotation.ConsumesAnnotation;
import org.exquery.restxq.annotation.HttpMethodAnnotation;
import org.exquery.restxq.annotation.HttpMethodWithBodyAnnotation;
import org.exquery.restxq.annotation.ParameterAnnotation;
import org.exquery.restxq.annotation.ProducesAnnotation;
import org.exquery.xdm.type.SequenceImpl;
import org.exquery.xdm.type.StringTypedValue;
import org.exquery.xquery.FunctionSignature;
import org.exquery.xquery.Sequence;
import org.exquery.xquery.TypedArgumentValue;

/**
*
* @author Adam Retter <adam.retter@googlemail.com>
*/
public abstract class AbstractRestXqService implements RestXqService {

    private final ResourceFunction resourceFunction;

    public AbstractRestXqService(final ResourceFunction resourceFunction) {
        this.resourceFunction = resourceFunction;
    }
   
    /**
     * @see org.exquery.restxq.RestXqService#getResourceFunction()
     */
    @Override
    public ResourceFunction getResourceFunction() {
        return resourceFunction;
    }
   
    /**
     * @see org.exquery.restxq.RestXqService#getServicedMethods()
     */
    @Override
    public EnumSet<HttpMethod> getServicedMethods() {
        final EnumSet<HttpMethod> servicedMethods = EnumSet.noneOf(HttpMethod.class);
        for(final HttpMethodAnnotation httpMethodAnnotation : getResourceFunction().getHttpMethodAnnotations()) {
            servicedMethods.add(httpMethodAnnotation.getHttpMethod());
        }
        return servicedMethods;
    }

    /**
     * Determines if this RESTXQ Service can service the request
     *
     * Rules are (must all apply):
     * 1) Can this Service service the HTTP Method of the request
     * 2) Does the ResourceFunction of this RESTXQ Service apply to the Request Path
     * 3) Can we consume the request i.e. Content-Type header
     * 4) Can we produce the response i.e. Accept header
     *
     * @see org.exquery.restxq.RestXqService#canService(org.exquery.http.HttpRequest)
     */
    @Override
    public boolean canService(final HttpRequest request) {
       
        //1) check the method matches
        if(getServicedMethods().contains(request.getMethod())) {
           
            //2) check the path matches
            if(getResourceFunction().getPathAnnotation() != null && !getResourceFunction().getPathAnnotation().matchesPath(request.getPath())) {
                return false;
            }
           
            //3) check we can consume the request
            if(!canServiceConsume(request)) {
                //TODO HTTP 415 - in RESTXQServiceRegistry.findService, if no service matches we should be able to return a HTTP reason!
                return false;
            }
               
            //4) check we can produce the request
            if(!canServiceProduce(request)) {
                //TODO HTTP 406 - in RESTXQServiceRegistry.findService, if no service matches we should be able to return a HTTP reason!
                return false;
            }
               
            return true;
        }
       
        return false;
    }

    private boolean canServiceConsume(final HttpRequest request) {
        if(getResourceFunction().getConsumesAnnotations().isEmpty()) {
           
            //if there are no constraints we can consume anything
            return true;
        } else {
            for(final ConsumesAnnotation consumesAnnotation : getResourceFunction().getConsumesAnnotations()) {
                if(consumesAnnotation.matchesMediaType(request)) {
                    return true;
                }
            }
        }
       
        return false;
    }
   
    private boolean canServiceProduce(final HttpRequest request) {
        if(getResourceFunction().getProducesAnnotations().isEmpty()) {
           
            //if there are no constraints we can produce anything
            return true;
        } else {
            for(final ProducesAnnotation producesAnnotation : getResourceFunction().getProducesAnnotations()) {
                if(producesAnnotation.matchesMediaType(request)) {
                    return true;
                }
            }
        }
       
        return false;
    }
   
    /**
     * @see org.exquery.restxq.RestXqService#maxProducesQualityFactory(org.exquery.http.AcceptHeader)
     */
    @Override
    public float maxProducesQualityFactor(final AcceptHeader acceptHeader) {
       
        //if there are no produces annotations, the quality factor is zero
        float max = 0;
       
        for(final Accept accept : acceptHeader.getAccepts()) {
            for(final ProducesAnnotation producesAnnotation : getResourceFunction().getProducesAnnotations()) {
                if(producesAnnotation.matchesMediaType(accept.getMediaRange())) {
                    if(accept.getQualityFactor() > max) {
                        max = accept.getQualityFactor();
                    }
                }
            }
        }
       
        return max;
    }
   
    /**
     * Service the request and send the response
     *
     * @see org.exquery.restxq.RestXqService#service(org.exquery.http.HttpRequest, org.exquery.http.HttpResponse, org.exquery.restxq.ResourceFunctionExecuter, org.exquery.restxq.RestXqServiceSerializer)
     */
    @Override
    public void service(final HttpRequest request, final HttpResponse response, final ResourceFunctionExecuter resourceFunctionExecuter, final RestXqServiceSerializer restXqServiceSerializer) throws RestXqServiceException {
       
        final Set<TypedArgumentValue> typedArgumentValues = extractParameters(request);
       
        final Sequence result = resourceFunctionExecuter.execute(getResourceFunction(), typedArgumentValues, request);
       
        restXqServiceSerializer.serialize(result, getResourceFunction().getSerializationAnnotations(), response);
    }

    /**
     * Gets the HTTP Method Annotations which potentially have Body Content
     *
     * @return The HTTP Method Annotations with a potential body parameter
     */
    protected Set<HttpMethodWithBodyAnnotation> getBodyContentAnnotations() {
        final Set<HttpMethodWithBodyAnnotation> bodyContentAnnotations = new HashSet<HttpMethodWithBodyAnnotation>();
        for(final HttpMethodAnnotation methodAnnotation : getResourceFunction().getHttpMethodAnnotations()) {
            if(methodAnnotation instanceof HttpMethodWithBodyAnnotation) {
                bodyContentAnnotations.add((HttpMethodWithBodyAnnotation)methodAnnotation);
            }
        }
        return bodyContentAnnotations;
    }
   
   
    /**
     * Extract Annotated Parameters from the Request
     *
     * @param request The HTTP Request to process
     *
     * @return The Map of Parameters to values, the key is the parameter
     * name and the value is the sequence of values extracted from the request
     *
     * @throws RestXqServiceException If an error occurred whilst processing the request
     */
    protected Set<TypedArgumentValue> extractParameters(final HttpRequest request) throws RestXqServiceException {
       
        final Set<TypedArgumentValue> paramNameValues = new HashSet<TypedArgumentValue>();
       
        //extract the param mappings for the Path Annotation
        if(getResourceFunction().getPathAnnotation() != null) {
            for(final Entry<String, String> pathParameter : getResourceFunction().getPathAnnotation().extractPathParameters(request.getPath()).entrySet()) {

                paramNameValues.add(new TypedArgumentValue<String>(){
                    @Override
                    public String getArgumentName() {
                        return pathParameter.getKey();
                    }

                    @Override
                    public Sequence<String> getTypedValue() {
                        return new SequenceImpl<String>(new StringTypedValue(pathParameter.getValue()));
                    }
                });
            }
        }
       
        //extract the param mappings for the Body Content Annotations
        if(!getBodyContentAnnotations().isEmpty()) {
            final Sequence requestBody = extractRequestBody(request);
            for(final HttpMethodWithBodyAnnotation bodyContentAnnotation : getBodyContentAnnotations()) {
                paramNameValues.add(new TypedArgumentValue(){
                    @Override
                    public String getArgumentName() {
                        return bodyContentAnnotation.getBodyParameterName();
                    }

                    @Override
                    public Sequence getTypedValue() {
                        return requestBody;
                    }
                });
            }
        }
       
        //extract the param mappings for Param Annotations
        for(final ParameterAnnotation parameterAnnotation : getResourceFunction().getParameterAnnotations()) {
            final TypedArgumentValue typedArgumentValue = parameterAnnotation.extractParameter(request);
            paramNameValues.add(new TypedArgumentValue(){

                @Override
                public String getArgumentName() {
                    return typedArgumentValue.getArgumentName();
                }

                @Override
                public Sequence getTypedValue() {
                    return typedArgumentValue.getTypedValue();
                }
            });
        }
       
        return paramNameValues;
    }
   
    /**
     * Extract the HTTP Request Body
     *
     * Implementations are free to return
     * a proxy which lazily extracts the request body
     * if desired.
     *
     * @param request The HTTP Request to extract the request body from
     *
     * @return The Sequence of values extracted from the request body,
     * typically a single item but possibly more for a multi-part request
     *
     * @throws RestXqServiceException If an error occurred whilst processing the Request Body
     */
    protected abstract Sequence extractRequestBody(final HttpRequest request) throws RestXqServiceException;
   
    /**
     * Generates a Hash Code for the RestXqService Object
     *
     * XORs together:
     *  1) the URI to the XQuery containing the Resource Function
     *  2) the name of the Resource Function
     *  3) the arity of the Resource Function
     */
    @Override
    public int hashCode() {
        final FunctionSignature fnSignature = getResourceFunction().getFunctionSignature();
        return getResourceFunction().getXQueryLocation().hashCode()
        ^ fnSignature.getName().hashCode()
        ^ fnSignature.getArgumentCount() * 32;
    }

    /**
     * Determines if this Service is equal to another Service
     *
     * Compares:
     *  1) the URI to the XQuery containing the Resource Function
     *  2) the name of the Resource Function
     *  3) the arity of the Resource Function
     */
    @Override
    public boolean equals(final Object obj) {
        final FunctionSignature fnSignature = getResourceFunction().getFunctionSignature();
       
        if(obj == null) {
            return false;
        }
       
        if(!(obj instanceof RestXqService)) {
            return false;
        }
       
        final RestXqService other = ((RestXqService)obj);
       
        return
            other.getResourceFunction().getXQueryLocation().equals(getResourceFunction().getXQueryLocation())
            && other.getResourceFunction().getFunctionSignature().getName().equals(fnSignature.getName())
            && other.getResourceFunction().getFunctionSignature().getArgumentCount() == fnSignature.getArgumentCount();
    }

    /**
     * Sorts the Services into Path Specificity descending order
     * That is to say that the resultant sorted list should have the most specific URI's at the top!
     *
     * @param other Another Service
     */
    @Override
    public int compareTo(final RestXqService other) {
        if(other == null || !(other instanceof RestXqService)) {
            return 1;
        }
       
        final long pathSpecificityMetric;
        if(getResourceFunction().getPathAnnotation() != null) {
            pathSpecificityMetric = getResourceFunction().getPathAnnotation().getPathSpecificityMetric();
        } else {
            pathSpecificityMetric = 0;
        }
       
        final long otherPathSpecificityMetric;
        if(other.getResourceFunction().getPathAnnotation() != null) {
            otherPathSpecificityMetric = other.getResourceFunction().getPathAnnotation().getPathSpecificityMetric();
        } else {
            otherPathSpecificityMetric = 0;
        }
       
        long result = otherPathSpecificityMetric - pathSpecificityMetric;
        if(result > 0) {
            return 1;
        } else if(result < 0) {
            return -1;
        } else {
            return 0;
        }
    }
}
TOP

Related Classes of org.exquery.restxq.impl.AbstractRestXqService

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.