All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.everrest.exoplatform.container.RestfulContainer Maven / Gradle / Ivy

There is a newer version: 1.12.3
Show newest version
/*******************************************************************************
 * Copyright (c) 2012-2014 Codenvy, S.A.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Codenvy, S.A. - initial API and implementation
 *******************************************************************************/
package org.everrest.exoplatform.container;

import org.everrest.core.Filter;
import org.everrest.core.impl.header.MediaTypeHelper;
import org.everrest.core.uri.UriPattern;
import org.exoplatform.container.ConcurrentPicoContainer;
import org.picocontainer.ComponentAdapter;
import org.picocontainer.PicoContainer;
import org.picocontainer.PicoRegistrationException;
import org.picocontainer.defaults.ComponentAdapterFactory;
import org.picocontainer.defaults.DefaultComponentAdapterFactory;
import org.picocontainer.defaults.DuplicateComponentKeyRegistrationException;

import javax.ws.rs.Consumes;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.ext.Providers;
import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Container intended for decoupling third part code and everrest framework. Components and adapters are searchable by
 * annotation and type or by annotation only. Components (implementation class or instance) annotated with JAX-RS
 * annotations @Path and @Provider should be registered in container as usually. After registration if class
 * annotated with {@link javax.ws.rs.Path} it may be retrieved by method {@link #getMatchedResource(String, List)}. If
 * class annotated with {@link javax.ws.rs.ext.Provider} and implement one of JAX-RS extension interfaces instance of
 * component may be retrieved by corresponded methods of {@link javax.ws.rs.ext.Providers} interface. E.g:
 * 

* Suppose we have a resource class org.example.MyResource annotated with @Path("my-resource") and base * URL is http://example.com/: *

*

*

 * @Path("my-resource")
 * public class MyResource
 * {
 *    @GET
 *    @Path("{id}")
 *    public String get()
 *    {
 *    ...
 *    }
 * }
 * 
*

* Need register resource in container. *

*

 * RestfulContainer container = ...
 * container.registerComponentImplementation(MyResource.class);
 * 
*

* Suppose we have a GET request for http://example.com/my-resource/101. We need to find resource matched * to relative path /my-resource/101. *

*

 * List<String> paramValues = new ArrayList<String>();
 * Object resource = container.getMatchedResource("/my-resource/101", paramValues);
 * ...
 * 
*

* Resource should be the instance of org.example.MyResource.class. Container supports injection JAX-RS runtime * information into a class field or constructor parameters with annotation {@link javax.ws.rs.core.Context}. *

* NOTE Methods registerXXX of this container may throws * {@link PicoRegistrationException} if component violates the restrictions of framework, e.g. if resource with the * same * URI pattern or provider with the same purpose already registered in this container. *

* * @author andrew00x * @see javax.ws.rs.core.Context * @see MessageBodyReader * @see MessageBodyWriter * @see ContextResolver * @see ExceptionMapper */ @SuppressWarnings("serial") public class RestfulContainer extends ConcurrentPicoContainer implements Providers { public RestfulContainer() { this(new DefaultComponentAdapterFactory(), null); } protected RestfulContainer(PicoContainer parent) { this(new DefaultComponentAdapterFactory(), parent); } protected RestfulContainer(ComponentAdapterFactory factory, PicoContainer parent) { super(wrapComponentAdapterFactory(factory), parent); } private static ComponentAdapterFactory wrapComponentAdapterFactory(ComponentAdapterFactory componentAdapterFactory) { return new RestfulComponentAdapterFactory(componentAdapterFactory); } private volatile Map restToComponentAdapters = new HashMap<>(); private final Lock lock = new ReentrantLock(); private static final class ProviderKey implements Key { private final Type type; private final Set consumes; private final Set produces; ProviderKey(Set consumes, Set produces, Type type) { this.consumes = consumes; this.produces = produces; this.type = type; } @Override public int hashCode() { int hash = 7; hash = hash * 31 + (consumes == null ? 0 : consumes.hashCode()); hash = hash * 31 + (produces == null ? 0 : produces.hashCode()); hash = hash * 31 + (type == null ? 0 : type.hashCode()); return hash; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } ProviderKey other = (ProviderKey)obj; if (consumes == null) { if (other.consumes != null) { return false; } } else if (!consumes.equals(other.consumes)) { return false; } if (produces == null) { if (other.produces != null) { return false; } } else if (!produces.equals(other.produces)) { return false; } if (type == null) { if (other.type != null) { return false; } } else if (!type.equals(other.type)) { return false; } return true; } } private static final class ResourceKey implements Key { private final UriPattern uriPattern; ResourceKey(UriPattern uriPattern) { this.uriPattern = uriPattern; } @Override public int hashCode() { int hash = 7; hash = hash * 31 + uriPattern.hashCode(); return hash; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null || obj.getClass() != getClass()) { return false; } return uriPattern.equals(((ResourceKey)obj).uriPattern); } } /** * Use such key in restToComponentAdapters to avoid duplicate restful components. It is not enough to * use just keys of component adapters since some resources or providers may be not unique for the rest framework, * e.g. : *
    *
  • resource classes may be different but may have the same or matched value of @Path annotation
  • *
  • two ExceptionMapper may process the same type of Exception
  • *
  • ...
  • *
*/ private static interface Key { } private static List makeKeys(RestfulComponentAdapter componentAdapter) { Class type = componentAdapter.getComponentImplementation(); if (type.isAnnotationPresent(Filter.class)) { // TODO: avoid duplication of few filters even if they are registered with different keys in container. } else if (type.isAnnotationPresent(Path.class)) { List keys = new ArrayList<>(1); keys.add(new ResourceKey(new UriPattern(type.getAnnotation(Path.class).value()))); return keys; } else if (type.isAnnotationPresent(Provider.class)) { ParameterizedType[] implementedInterfaces = componentAdapter.getImplementedInterfaces(); List keys = new ArrayList<>(implementedInterfaces.length); for (ParameterizedType genericInterface : implementedInterfaces) { Class rawType = (Class)genericInterface.getRawType(); // @Consumes makes sense for MessageBodyReader ONLY Set consumes = MessageBodyReader.class == rawType // ? new HashSet<>(MediaTypeHelper.createConsumesList(type.getAnnotation(Consumes.class))) : null; // @Produces makes sense for MessageBodyWriter or ContextResolver Set produces = ContextResolver.class == rawType || MessageBodyWriter.class == rawType// ? new HashSet<>(MediaTypeHelper.createProducesList(type.getAnnotation(Produces.class))) : null; keys.add(new ProviderKey(consumes, produces, genericInterface)); } return keys; } return Collections.emptyList(); } @Override public ComponentAdapter registerComponent(ComponentAdapter componentAdapter) throws DuplicateComponentKeyRegistrationException { if (componentAdapter instanceof RestfulComponentAdapter) { List keys = makeKeys((RestfulComponentAdapter)componentAdapter); if (keys.size() > 0) { lock.lock(); try { Map copy = new HashMap<>(restToComponentAdapters); for (Key key : keys) { ComponentAdapter previous = copy.put(key, componentAdapter); if (previous != null) { throw new PicoRegistrationException("Cannot register component " + componentAdapter + " because already registered component " + previous); } } super.registerComponent(componentAdapter); restToComponentAdapters = copy; return componentAdapter; } finally { lock.unlock(); } } } return super.registerComponent(componentAdapter); } @Override public ComponentAdapter registerComponentInstance(Object componentKey, Object componentInstance) throws PicoRegistrationException { if (RestfulComponentAdapter.isRestfulComponent(componentInstance)) { ComponentAdapter componentAdapter = new RestfulComponentAdapter(componentKey, componentInstance); registerComponent(componentAdapter); return componentAdapter; } return super.registerComponentInstance(componentKey, componentInstance); } @Override public ComponentAdapter unregisterComponent(Object componentKey) { ComponentAdapter componentAdapter = super.unregisterComponent(componentKey); if (componentAdapter instanceof RestfulComponentAdapter) { List keys = makeKeys((RestfulComponentAdapter)componentAdapter); if (keys.size() > 0) { lock.lock(); try { Map copy = new HashMap<>(restToComponentAdapters); copy.keySet().removeAll(keys); restToComponentAdapters = copy; } finally { lock.unlock(); } } } return componentAdapter; } // /** * Retrieve all the component adapters for types annotated with annotation inside this container. The component adapters * from the parent container are not returned. * * @param annotation * the annotation type * @return a collection containing all the ComponentAdapters for types annotated with annotation inside this container. */ @SuppressWarnings("unchecked") public List getComponentAdapters(Class annotation) { Collection adapters = getComponentAdapters(); if (adapters.size() > 0) { List result = new ArrayList<>(); for (ComponentAdapter a : adapters) { if (a.getComponentImplementation().isAnnotationPresent(annotation)) { result.add(a); } } return result; } return Collections.emptyList(); } /** * Retrieve all the component adapters of the specified type and annotated with annotation inside this container. The * component adapters from the parent container are not returned. * * @param componentType * the type of component * @param annotation * the annotation type * @return a collection containing all the ComponentAdapters of the specified type and annotated with annotation inside * this container. */ @SuppressWarnings({"unchecked"}) public List getComponentAdaptersOfType(Class componentType, Class annotation) { List adapters = getComponentAdaptersOfType(componentType); if (adapters.size() > 0) { List result = new ArrayList<>(); for (ComponentAdapter a : adapters) { if (a.getComponentImplementation().isAnnotationPresent(annotation)) { result.add(a); } } return result; } return Collections.emptyList(); } /** * Retrieve all the component instances of the specified type and annotated with annotation. * * @param componentType * the type of component * @param annotation * the annotation type * @return a collection of components */ public List getComponentsOfType(Class componentType, Class annotation) { List instances = getComponentInstancesOfType(componentType); if (instances.size() > 0) { List result = new ArrayList<>(); for (Object o : instances) { if (o.getClass().isAnnotationPresent(annotation)) { result.add(componentType.cast(o)); } } return result; } return Collections.emptyList(); } /** * Retrieve all the component instances annotated with annotation. * * @param annotation * the annotation type * @return a collection of components */ public List getComponents(Class annotation) { List instances = getComponentInstances(); if (instances.size() > 0) { List result = new ArrayList<>(); for (Object o : instances) { if (o.getClass().isAnnotationPresent(annotation)) { result.add(o); } } return result; } return Collections.emptyList(); } // ------- Resources -------- /** * Get ComponentAdapter root resource matched to requestPath. * * @param requestPath * request path * @param parameterValues * list for placing values of URI templates * @return root resource matched to requestPath or null */ public final ComponentAdapter getMatchedResource(String requestPath, List parameterValues) { return ComponentsFinder.findResource(this, requestPath, parameterValues); } // -------- Providers -------- @Override public final MessageBodyReader getMessageBodyReader(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { return ComponentsFinder.findReader(this, type, genericType, annotations, mediaType); } @Override public final MessageBodyWriter getMessageBodyWriter(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { return ComponentsFinder.findWriter(this, type, genericType, annotations, mediaType); } @Override public final ExceptionMapper getExceptionMapper(Class type) { return ComponentsFinder.findExceptionMapper(this, type); } @Override public final ContextResolver getContextResolver(Class contextType, MediaType mediaType) { return ComponentsFinder.findContextResolver(this, contextType, mediaType); } }