org.apache.sling.stanbol.rest.ported.JerseyUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.sling.stanbol.rest.ported.JerseyUtils.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.sling.stanbol.rest.ported;

import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
import static org.apache.clerezza.rdf.core.serializedform.SupportedFormat.N3;
import static org.apache.clerezza.rdf.core.serializedform.SupportedFormat.N_TRIPLE;
import static org.apache.clerezza.rdf.core.serializedform.SupportedFormat.RDF_JSON;
import static org.apache.clerezza.rdf.core.serializedform.SupportedFormat.RDF_XML;
import static org.apache.clerezza.rdf.core.serializedform.SupportedFormat.TURTLE;
import static org.apache.clerezza.rdf.core.serializedform.SupportedFormat.X_TURTLE;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
import java.net.URLDecoder;
import java.nio.charset.UnsupportedCharsetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.MessageBodyReader;

import org.apache.commons.io.IOUtils;
import org.apache.stanbol.entityhub.core.query.DefaultQueryFactory;
import org.apache.stanbol.entityhub.servicesapi.model.Entity;
import org.apache.stanbol.entityhub.servicesapi.model.Representation;
import org.apache.stanbol.entityhub.servicesapi.query.FieldQuery;
import org.apache.stanbol.entityhub.servicesapi.query.FieldQueryFactory;
import org.apache.stanbol.entityhub.servicesapi.query.QueryResultList;
import org.apache.stanbol.entityhub.servicesapi.query.TextConstraint;
import org.apache.stanbol.entityhub.servicesapi.query.TextConstraint.PatternType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Utility methods used by several of the RESTful service endpoints of the
 * Entityhub.
 * @author Rupert Westenthaler
 *
 */
public final class JerseyUtils {

    private static Logger log = LoggerFactory.getLogger(JerseyUtils.class);
    /**
     * Unmodifiable Set with the Media Types supported for {@link Representation}
     */
    public static final Set<String> REPRESENTATION_SUPPORTED_MEDIA_TYPES = Collections
            .unmodifiableSet(new HashSet<String>(
                    Arrays.asList(APPLICATION_JSON, RDF_XML, N3, TURTLE, X_TURTLE, RDF_JSON, N_TRIPLE)));
    /**
     * Unmodifiable Set with the Media Types supported for {@link Entity}
     */
    public static final Set<String> ENTITY_SUPPORTED_MEDIA_TYPES = REPRESENTATION_SUPPORTED_MEDIA_TYPES;

    /**
     * Unmodifiable Set with the Media Types supported for {@link QueryResultList}
     */
    public static final Set<String> QUERY_RESULT_SUPPORTED_MEDIA_TYPES = REPRESENTATION_SUPPORTED_MEDIA_TYPES;
    /**
     * This utility class used the {@link DefaultQueryFactory} as
     * {@link FieldQueryFactory} instance. 
     */
    private static FieldQueryFactory queryFactory = DefaultQueryFactory.getInstance();

    private JerseyUtils() {
        /* do not create instance of Util Classes */}

    /**
     * Searches the Header for acceptable media types and returns the first found
     * that is not the wildcard type. If no one is found the parsed default type
     * is returned.
     *
     * @param headers the request headers
     * @param defaultType the default type if no or only the wildcard type was found in
     * the header
     * @return the acceptable media type
     */
    public static MediaType getAcceptableMediaType(HttpHeaders headers, MediaType defaultType) {
        MediaType acceptedMediaType = null;
        if (!headers.getAcceptableMediaTypes().isEmpty()) {
            for (MediaType accepted : headers.getAcceptableMediaTypes()) {
                if (!accepted.isWildcardType()) {
                    acceptedMediaType = accepted;
                    break;
                }
            }
        }
        if (acceptedMediaType == null) {
            acceptedMediaType = defaultType;
        }
        return acceptedMediaType;
    }

    /**
     * Checks the parsed MediaTypes for supported types. WildCards are not
     * supported by this method. If no supported is found the defaultType
     * is returned
     * @param headers the headers of the request
     * @param supported the supported types
     * @param defaultType the default type used of no match is found
     * @return the first supported media type part of the header or the default
     * type 
     */
    public static MediaType getAcceptableMediaType(HttpHeaders headers, Collection<String> supported,
            MediaType defaultType) {
        MediaType acceptedMediaType = null;
        if (!headers.getAcceptableMediaTypes().isEmpty()) {
            for (MediaType accepted : headers.getAcceptableMediaTypes()) {
                if (!accepted.isWildcardType()
                        && supported.contains(accepted.getType() + '/' + accepted.getSubtype())) {
                    acceptedMediaType = accepted;
                    break;
                }
            }
        }
        if (acceptedMediaType == null) {
            acceptedMediaType = defaultType;
        }
        return acceptedMediaType;
    }

    //    /**
    //     * Returns the {@link FieldQuery} based on the JSON formatted String (in case
    //     * of "application/x-www-form-urlencoded" requests) or file (in case of
    //     * "multipart/form-data" requests).<p>
    //     * @param query the string containing the JSON serialised FieldQuery or
    //     * <code>null</code> in case of a "multipart/form-data" request
    //     * @param file the temporary file holding the data parsed by the request to
    //     * the web server in case of a "multipart/form-data" request or <code>null</code>
    //     * in case of the "application/x-www-form-urlencoded" request.
    //     * @return the FieldQuery parsed from the string provided by one of the two
    //     * parameters
    //     * @throws WebApplicationException if both parameter are <code>null</code> or
    //     * if the string provided by both parameters could not be used to parse a
    //     * {@link FieldQuery} instance.
    //     */
    //    public static FieldQuery parseFieldQuery(String query, File file) throws WebApplicationException {
    //        if(query == null && file == null) {
    //            throw new WebApplicationException(new IllegalArgumentException("Query Requests MUST define the \"query\" parameter"), Response.Status.BAD_REQUEST);
    //        }
    //        FieldQuery fieldQuery = null;
    //        JSONException exception = null;
    //        if(query != null){
    //            try {
    //                fieldQuery = JSONToFieldQuery.fromJSON(queryFactory,query);
    //            } catch (JSONException e) {
    //                log.warn("unable to parse FieldQuery from \"application/x-www-form-urlencoded\" encoded query string "+query,e);
    //                fieldQuery = null;
    //                exception = e;
    //            }
    //        } //else no query via application/x-www-form-urlencoded parsed
    //        if(fieldQuery == null && file != null){
    //            try {
    //                query = FileUtils.readFileToString(file);
    //                fieldQuery = JSONToFieldQuery.fromJSON(queryFactory,query);
    //            } catch (IOException e) {
    //                throw new WebApplicationException(e, Response.Status.INTERNAL_SERVER_ERROR);
    //            } catch (JSONException e) {
    //                log.warn("unable to parse FieldQuery from \"multipart/form-data\" encoded query string "+query,e);
    //                exception = e;
    //            }
    //        }//fieldquery already initialised or no query via multipart/form-data parsed
    //        if(fieldQuery == null){
    //            throw new WebApplicationException(new IllegalArgumentException("Unable to parse FieldQuery form the parsed query String:"+query, exception),Response.Status.BAD_REQUEST);
    //        }
    //        return fieldQuery;
    //    }
    /**
     * Creates an {@link FieldQuery} for parameters parsed by the /find requests
     * supported by the /symbol, /sites and {siteId} RESTful endpoints.
     * TODO: This has to be refactored to use "EntityQuery" as soon as Multiple
     *       query types are implemented for the Entityhub.
     * @param name the name pattern to search entities for (required)
     * @param field the field used to search for entities (required)
     * @param language the language of the parsed name pattern (optional)
     * @param limit the maximum number of result (optional)
     * @param offset the offset of the first result (optional)
     * @return the {@link FieldQuery} representing the parsed parameter
     * @throws WebApplicationException in case the parsed name pattern is invalid.
     * The validation of this required parameter provided by the Request is done
     * by this method.
     * @throws IllegalArgumentException in case the parsed field is invalid. Callers
     * of this method need to ensure that this parameter is set to an valid value.
     */
    public static FieldQuery createFieldQueryForFindRequest(String name, String field, String language,
            Integer limit, Integer offset) throws WebApplicationException, IllegalArgumentException {
        if (name == null || name.trim().isEmpty()) {
            // This throws an WebApplicationException, because the search name is
            // provided by the caller. So an empty or missing name is interpreted
            // as an bad Requested sent by the user
            throw new WebApplicationException(
                    new IllegalArgumentException(
                            "The parsed \"name\" pattern to search entities for MUST NOT be NULL nor EMPTY"),
                    Response.Status.BAD_REQUEST);
        } else {
            name = name.trim();
        }
        if (field == null || field.trim().isEmpty()) {
            // This throws no WebApplicationException, because "field" is an 
            // optional parameter and callers of this method MUST provide an
            // valid default value in case the request does not provide any or
            // valid data. 
            new IllegalArgumentException("The parsed search \"field\" MUST NOT be NULL nor EMPTY");
        } else {
            field = field.trim();
        }
        log.debug("Process Find Request:");
        log.debug("  > name  : " + name);
        log.debug("  > field  : " + field);
        log.debug("  > lang  : " + language);
        log.debug("  > limit : " + limit);
        log.debug("  > offset: " + offset);
        FieldQuery query = queryFactory.createFieldQuery();
        if (language == null) {
            query.setConstraint(field, new TextConstraint(name, PatternType.wildcard, false));
        } else {
            query.setConstraint(field, new TextConstraint(name, PatternType.wildcard, false, language));
        }
        Collection<String> selectedFields = new ArrayList<String>();
        selectedFields.add(field); //select also the field used to find entities
        query.addSelectedFields(selectedFields);
        if (limit != null && limit > 0) {
            query.setLimit(limit);
        }
        if (offset != null && offset > 0) {
            query.setOffset(offset);
        }
        return query;
    }

    //    /**
    //     * Getter for a Service from the {@link ServletContext} by using the
    //     * {@link Class#getName()} as key for {@link ServletContext#getAttribute(String)}.
    //     * In case the Service can not be found a {@link WebApplicationException} is
    //     * thrown with the message that the Service is currently not available.
    //     * @param <T> The type of the Service
    //     * @param service the Service interface
    //     * @param context the context used to search the service
    //     * @return the Service instance
    //     * @throws WebApplicationException in case the service instance was not found 
    //     * in the parsed servlet context
    //     * @throws IllegalArgumentException if <code>null</code> is parsed as
    //     * service or context
    //     */
    //    @SuppressWarnings("unchecked")
    //    public static <T> T getService(Class<T> service, ServletContext context) throws WebApplicationException, IllegalArgumentException {
    //        if(service == null){
    //            throw new IllegalArgumentException("The parsed ServiceInterface MUST NOT be NULL!");
    //        }
    //        if(context == null){
    //            throw new IllegalArgumentException("The parsed ServletContext MUST NOT be NULL");
    //        }
    //        T serviceInstance = (T) context.getAttribute(service.getName());
    //        if(serviceInstance == null){
    //            throw new WebApplicationException(new IllegalStateException(
    //                "The "+service.getSimpleName()+" Service is currently not available " +
    //                      "(full name= "+service+"| " +
    //                            "servlet context name = "+context.getServletContextName()+")"), 
    //                Response.Status.INTERNAL_SERVER_ERROR);
    //        }
    //        return serviceInstance;
    //    }
    /**
     * Tests if a generic type (may be &lt;?&gt;, &lt;? extends {required}&gt; 
     * or &lt;? super {required}&gt;) is compatible with the required one.
     * TODO: Should be moved to an utility class
     * @param required the required class the generic type MUST BE compatible with
     * @param genericType the required class
     * @return if the generic type is compatible with the required class
     */
    public static boolean testType(Class<?> required, Type genericType) {
        //for the examples let assume that a Set is the raw type and the
        //requested generic type is a Representation with the following class
        //hierarchy:
        // Object
        //     -> Representation
        //         -> RdfRepresentation
        //         -> InMemoryRepresentation
        //     -> InputStream
        //     -> Collection<T>
        boolean typeOK;
        if (genericType instanceof Class<?>) {
            //OK
            //  Set<Representation>
            //  Set<Object>
            //NOT OK
            //  Set<RdfRepresentation>
            //  Set<InputStream>
            typeOK = ((Class<?>) genericType).isAssignableFrom(required);
        } else if (genericType instanceof WildcardType) {
            //In cases <? super {class}>, <? extends {class}, <?>
            WildcardType wildcardSetType = (WildcardType) genericType;
            if (wildcardSetType.getLowerBounds().length > 0) {
                Type lowerBound = wildcardSetType.getLowerBounds()[0];
                //OK
                //  Set<? super RdfRepresentation>
                //  Set<? super Representation>
                //NOT OK
                //  Set<? super InputStream>
                //  Set<? super Collection<Representation>>
                typeOK = lowerBound instanceof Class<?> && required.isAssignableFrom((Class<?>) lowerBound);
            } else if (wildcardSetType.getUpperBounds().length > 0) {
                Type upperBound = wildcardSetType.getUpperBounds()[0];
                //OK
                //  Set<? extends Representation>
                //  Set<? extends Object>
                //NOT OK
                //  Set<? extends RdfRepresentation>
                //  Set<? extends InputStream>
                //  Set<? extends Collection<Representation>
                typeOK = upperBound instanceof Class<?> && ((Class<?>) upperBound).isAssignableFrom(required);
            } else { //no upper nor lower bound
                // Set<?>
                typeOK = true;
            }
        } else if (required.isArray() && genericType instanceof GenericArrayType) {
            //In case the required type is an array we need also to support 
            //possible generic Array specifications
            GenericArrayType arrayType = (GenericArrayType) genericType;
            typeOK = testType(required.getComponentType(), arrayType.getGenericComponentType());
        } else {
            //GenericArrayType but !required.isArray() -> incompatible
            //TypeVariable -> no variables define -> incompatible
            typeOK = false;
        }
        return typeOK;
    }

    /**
     * This Method is intended to parse form data from 
     * {@link MediaType#APPLICATION_FORM_URLENCODED} requests. This functionality
     * us usually needed when writing a {@link MessageBodyReader} to get the
     * data from the "{@link InputStream} entityStream" parameter of the 
     * {@link MessageBodyReader#readFrom(Class, Type, java.lang.annotation.Annotation[], MediaType, javax.ws.rs.core.MultivaluedMap, InputStream)}
     * method.
     * @param entityStream the stream with the form data
     * @param charset The charset used for the request (if <code>null</code> or
     * empty UTF-8 is used as default.
     * @return the parsed form data as key value map
     * @throws IOException on any exception while reading the data form the stream
     */
    public static Map<String, String> parseForm(InputStream entityStream, String charset) throws IOException {
        /* TODO: Question: 
         * If I get an Post Request with "application/x-www-form-urlencoded" 
         * and a charset (lets assume "iso-2022-kr") do I need to use the 
         * charset to read the String from the Stream, or to URL decode the 
         * String or both?
         * 
         * This code assumes that it needs to be used for both, but this needs
         * validation!
         */
        if (charset == null || charset.isEmpty()) {
            charset = "UTF-8";
        }
        String data;
        try {
            data = IOUtils.toString(entityStream, charset);
        } catch (UnsupportedCharsetException e) {
            throw new IOException(e.getMessage(), e);
        }
        Map<String, String> form = new HashMap<String, String>();
        StringTokenizer tokenizer = new StringTokenizer(data, "&");
        String token;
        try {
            while (tokenizer.hasMoreTokens()) {
                token = tokenizer.nextToken();
                int index = token.indexOf('=');
                if (index < 0) {
                    form.put(URLDecoder.decode(token, charset), null);
                } else if (index > 0) {
                    form.put(URLDecoder.decode(token.substring(0, index), charset),
                            URLDecoder.decode(token.substring(index + 1), charset));
                }
            }
        } catch (UnsupportedCharsetException e) {
            throw new IOException(e.getMessage(), e);
        }
        return form;
    }

}