Package org.apache.clerezza.triaxrs

Source Code of org.apache.clerezza.triaxrs.JaxRsHandler

/*
* 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.clerezza.triaxrs;

import org.apache.clerezza.jaxrs.extensions.MethodResponse;
import org.apache.clerezza.jaxrs.extensions.ResourceMethodException;
import org.apache.clerezza.jaxrs.extensions.RootResourceExecutor;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.lang.annotation.Annotation;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.security.AccessControlException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.Path;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.ext.Providers;

import org.osgi.framework.ServiceReference;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.clerezza.jaxrs.extensions.prefixmanager.BundlePrefixManager;
import org.apache.clerezza.triaxrs.providers.AggregatedProviders;
import org.apache.clerezza.triaxrs.providers.CascadingProviders;
import org.apache.clerezza.triaxrs.providers.DefaultProviders;
import org.apache.clerezza.triaxrs.providers.ProvidersImpl;
import org.apache.clerezza.triaxrs.util.MethodUtil;
import org.apache.clerezza.triaxrs.util.PathMatching;
import org.wymiwyg.wrhapi.Handler;
import org.wymiwyg.wrhapi.HandlerException;
import org.wymiwyg.wrhapi.HeaderName;
import org.wymiwyg.wrhapi.Method;
import org.wymiwyg.wrhapi.Request;
import org.wymiwyg.wrhapi.RequestURI.Type;
import org.wymiwyg.wrhapi.Response;
import org.wymiwyg.wrhapi.ResponseStatus;
import org.wymiwyg.wrhapi.util.MessageBody2Read;

/**
*
* Am implementation of JAX-RS (aka JSR 311) based on wrhapi.
*
* It supports both injection of <code>javax.ws.rs.core.Application</code>
* instances, as well as direct injection of <code>Provider</code>s and root
* resources. For direct injection a component just need to expose a service of
* type <code>java.lang.Object</code> and have the property "javax.ws.rs" set to
* true.
*
* @author reto
*
* @scr.component name="org.apache.clerezza.triaxrs.JaxRsHandler"
* @scr.service interface="org.wymiwyg.wrhapi.Handler"
* @scr.reference name="component" cardinality="0..n" policy="dynamic"
*                interface="java.lang.Object" target="(javax.ws.rs=true)"
* @scr.reference name="bundlePrefixManager" cardinality="0..1" policy="dynamic"
*                interface="org.apache.clerezza.jaxrs.extensions.prefixmanager.BundlePrefixManager"
*/
public class JaxRsHandler implements Handler {
 
  private static Logger logger = LoggerFactory.getLogger(JaxRsHandler.class);

  /**
   * applicationConfigs are injected by the osgi framework. At the moment, we
   * can handle one application per handler.
   *
   * @scr.reference cardinality="0..1"
   */
  Application applicationConfig;
 
  /**
   * @scr.reference
   */
  private RootResourceExecutor resourceExecutor;

  /**
   * stores all <code>ServiceReference</code>s of <code>Application</code>s
   * that were bound before the JaxRsHandler was activated.
   */
  private Set<ServiceReference> applicationReferenceStore = new HashSet<ServiceReference>();

  /**
   * stores all <code>ServiceReference</code>s of component objects (where
   * javax.ws.rs=true) that were bound before the JaxRsHandler was activated.
   */
  Set<ServiceReference> componentReferenceStore = new HashSet<ServiceReference>();

  /**
   * manages the set of root-resources
   */
  private RootResources rootResources;

  /**
   * the resource descriptor provided by the application, so they can be
   * removed when the application is removed
   */
  Collection<RootResourceDescriptor> applicationProvidedDescriptors =
      new ArrayList<RootResourceDescriptor>();

  /**
   * Lock used when changing the configuration.
   */
  private static ReentrantReadWriteLock configLock = new ReentrantReadWriteLock();

  /**
   * Provides access to the providers, this aggregation of (in order): -
   * applicationProviders (if available) - componentSpecifiedProviders -
   * built-in providers
   */
  public static final AggregatedProviders providers = new AggregatedProviders(configLock);

  /**
   * Providers injected with OSGi DS
   */
  private CascadingProviders componentSpecifiedProviders = new CascadingProviders();

  /**
   * ComponentContext injected in the activate()-Method by OSGi DS
   */
  private ComponentContext componentContext;

  /**
   * Keeps the mappings form bundles symbolic names to a bundle prefixes
   */
  private BundlePrefixManager prefixManager;

  /**
   * Keeps the mappings from components (providers) to their path prefixes.
   * This is necessary because during unbindComponent we might not have
   * access to prefixManager anymore (since it might have been unbound).
   */
  private Map<Object, String> component2PathPrefixMap =
      new HashMap<Object, String>();

  /**
   * Keeps the WebRequest for a thread.
   */
  public static ThreadLocal<WebRequest> localRequest = new ThreadLocal<WebRequest>();

  private Set<HttpMethod> httpMethods = new HashSet<HttpMethod>();

  public JaxRsHandler() {
    // initialize default providers
    Class<?>[] defaultProviders = DefaultProviders.getDefaultProviders();
    Providers builtInProviders = new ProvidersImpl(defaultProviders);
    providers.reset(componentSpecifiedProviders,
        builtInProviders);
    rootResources = new RootResources();
    resourceExecutor = new RootResourceExecutorImpl();
  }
 
  protected void bindBundlePrefixManager(BundlePrefixManager prefixManager) {
    logger.debug("Binding bundle prefix manager");
    configLock.writeLock().lock();
    try {
      this.prefixManager = prefixManager;
      registerFromStores();
    } finally {
      configLock.writeLock().unlock();
    }
  }
 
  protected void unbindBundlePrefixManager(BundlePrefixManager prefixManager) {
    logger.debug("Unbinding bundle prefix manager");
    configLock.writeLock().lock();
    try {
      this.prefixManager = null;
    } finally {
      configLock.writeLock().unlock();
    }
  }

  /**
   * Binds the specified JAX-RS root-resource or provider.
   *
   * @param component
   *            The new JAX-RS component to bind.
   */
  protected void bindComponent(ServiceReference serviceReference) {
    logger.debug("Bind component of bundle {}", serviceReference
        .getBundle().getSymbolicName());
    configLock.writeLock().lock();
    try {
      if (componentContext != null && prefixManager != null) {
        registerComponent(serviceReference);
      } else {
        componentReferenceStore.add(serviceReference);
      }
    } finally {
      configLock.writeLock().unlock();
    }
  }

  private void registerComponent(ServiceReference serviceReference) {
    String bundlePathPrefix = prefixManager.getPrefix(serviceReference
        .getBundle());

    Object component = componentContext.getBundleContext().getService(serviceReference);
    if (component == null) {
      return;
    }
    registerComponent(component, bundlePathPrefix);
    component2PathPrefixMap.put(component, bundlePathPrefix);
  }

  protected void registerComponent(Object component, String pathPrefix) {     
    final Class<?> clazz = component.getClass();
    final Path path = clazz.getAnnotation(Path.class);
    if (path == null) {
      // Warn about and ignore classes that do not conform to the
      // requirements of root resource or provider classes.

      if (clazz.getAnnotation(Provider.class) != null) {
        logger.info("Register provider {} to path {}", component.getClass().getName(), pathPrefix);
        componentSpecifiedProviders.addInstance(component, pathPrefix);
      } else {
        logger.warn("Ignoring component: {} (Not a root resource or provider)",
            component);
      }
    } else {
      logger.info("Register resource {} to path {}{}",
          new Object[]{component.getClass().getName(), pathPrefix, path.value()});
      collectHttpMethods(component.getClass());
      final RootResourceDescriptor descriptor = new RootResourceDescriptor(
          clazz, component, pathPrefix + path.value(), providers);
      rootResources.add(descriptor);
      // FIXME An unbind will potentially remove the wrong
      // descriptor from the set. Solution: Use a list or a
      // lookup table.
    }
  }

  private void collectHttpMethods(Class<?> clazz) {
    Set<java.lang.reflect.Method> annotatedMethods = MethodUtil.getAnnotatedMethods(clazz);
    for (java.lang.reflect.Method method : annotatedMethods) {
      Annotation[] annotations = method.getAnnotations();
      for (Annotation annotation : annotations) {
        HttpMethod httpMethod = annotation.annotationType().getAnnotation(HttpMethod.class);
        if (httpMethod != null) {
          httpMethods.add(httpMethod);
        }
      }
    }
  }

  /**
   * Unbinds the specified JAXRS component referred by the parameter.
   *
   */
  protected void unbindComponent(ServiceReference serviceReference) {
    configLock.writeLock().lock();
    try {
      if (!componentReferenceStore.remove(serviceReference)) {
        Object component = componentContext.getBundleContext().getService(serviceReference);
        if (component == null) {
          logger.warn("Failed to unregister {} as no service could be located", serviceReference);
          return;
        }
        unregisterComponent(component, component2PathPrefixMap.get(component));
        component2PathPrefixMap.remove(component);
      }
    } finally {
      configLock.writeLock().unlock();
    }
  }

  protected void unregisterComponent(Object component, String pathPrefix) {
    final Class<?> clazz = component.getClass();
    final Path path = clazz.getAnnotation(Path.class);
    logger.info("Unbinding: {}", component);
    if (path != null) {
      final RootResourceDescriptor descriptor = new RootResourceDescriptor(
          clazz, component, path.value(), providers);
      rootResources.remove(descriptor);
      // FIXME An unbind will potentially remove the wrong
      // descriptor from the set. Solution: Use a list or a
      // lookup table.
      // why? the component is unique and two root-resource
      // descriptors aren't equals without the same component
    } else if (clazz.getAnnotation(Provider.class) != null) {
      componentSpecifiedProviders.removeInstance(component, pathPrefix);
    }
  }

  /**
   * this is called when a new application config arrives
   *
   * @param applicationConfig
   */
  protected void bindApplicationConfig(ServiceReference serviceReference) {
    configLock.writeLock().lock();
    try {
      if (componentContext != null && prefixManager != null) {
        registerApplicationConfig(serviceReference);
      } else {
        applicationReferenceStore.add(serviceReference);
      }
    } finally {
      configLock.writeLock().unlock();
    }
  }

  private void registerApplicationConfig(ServiceReference serviceReference) {
    String bundlePathPrefix = prefixManager.getPrefix(serviceReference
        .getBundle());

        Application applicationConfigToRegister = (Application) componentContext
        .locateService("applicationConfig", serviceReference);
    if(applicationConfigToRegister == null) {
      return;
    }

    registerApplicationConfig(applicationConfigToRegister, bundlePathPrefix);
  }

  protected void registerApplicationConfig(Application applicationConfig,
      String pathPrefix) {
    logger.info("Binding application config: {} ({})", applicationConfig,
        applicationConfig.getClass());

    this.applicationConfig = applicationConfig;
    Providers[] delegates = providers.getDelegates();
    if (delegates.length != 2) {
      throw new RuntimeException(
          "expecting service discovered and buil-in providers");
    }
    CascadingProviders applicationProviders = new CascadingProviders();
    providers.reset(applicationProviders, delegates[0], delegates[1]);
    Set<Class<?>> appProvidedClasses = applicationConfig.getClasses();
    // TODO add application provider in a way that they are removable
    // and that

    for (Class<?> clazz : appProvidedClasses) {
      registerSingleComponentOfApplication(clazz, null,
          applicationProviders, pathPrefix);
    }
    Set<Object> singletons = applicationConfig.getSingletons();
    for (Object singleton : singletons) {
      Class<?> clazz = singleton.getClass();
      registerSingleComponentOfApplication(clazz, singleton,
          applicationProviders, pathPrefix);
    }

    logger.debug("Binding application config finished.");
  }

  protected void registerSingleComponentOfApplication(Class<?> clazz,
      Object instance, CascadingProviders applicationProviders,
      String pathPrefix) {
    Path path = clazz.getAnnotation(Path.class);
    if (path != null) {
      RootResourceDescriptor rootResourceDescriptor;
      collectHttpMethods(clazz);
      String completePath = pathPrefix + path.value();
      if (instance == null) {
        rootResourceDescriptor = new RootResourceDescriptor(clazz,
            completePath);
      } else {
        rootResourceDescriptor = new RootResourceDescriptor(clazz,
            instance, completePath, providers);
      }
      rootResources.add(rootResourceDescriptor);
      applicationProvidedDescriptors.add(rootResourceDescriptor);

    } else {
      if (clazz.getAnnotation(Provider.class) != null) {
        if (instance != null) {
          applicationProviders.addInstance(instance, pathPrefix);
        } else {
          applicationProviders.addClass(clazz, pathPrefix);
        }
      } else {
        logger.warn("Ignoring application component: {}" +
            " (Not a root resource or provider)", clazz);
      }
    }
  }

  protected void unbindApplicationConfig(Application applicationConfig) {
    configLock.writeLock().lock();
    try {
      this.unregisterApplicationConfig(applicationConfig);
    } finally {
      configLock.writeLock().unlock();
    }
  }

  protected void unregisterApplicationConfig(Application applicationConfig) {
    logger.info("unbinding app config.");
    this.applicationConfig = null;
    Providers[] delegates = providers.getDelegates();
    if (delegates.length != 3) {
      throw new RuntimeException("expecting application provided,"
          + " service discovered and buil-in providers");
    }
    providers.reset(delegates[1], delegates[2]);
    for (RootResourceDescriptor rootResourceDescriptor : applicationProvidedDescriptors) {
      rootResources.remove(rootResourceDescriptor);
    }
    applicationProvidedDescriptors.clear();
  }

  /**
   * The activate method is called when SCR activates the component
   * configuration
   *
   * @param componentContext
   */
  protected void activate(ComponentContext componentContext) {
    configLock.writeLock().lock();
    try {
      this.componentContext = componentContext;
      registerFromStores();
    } finally {
      configLock.writeLock().unlock();
    }
  }

  private void registerFromStores() {
    if (componentContext != null && prefixManager != null) { 
     
      for (ServiceReference appRef : applicationReferenceStore) {
        this.registerApplicationConfig(appRef);
      }
      applicationReferenceStore.clear();
 
      for (ServiceReference compRef : componentReferenceStore) {
        this.registerComponent(compRef);
      }
      componentReferenceStore.clear();
    }
  }

  @Override
  public void handle(Request origRequest, Response response)
      throws HandlerException {


    WebRequest request = new WebRequestImpl(origRequest, providers);
    localRequest.set(request);

    try {
      MethodResponse methodResponse;
      Type type = request.getWrhapiRequest().getRequestURI().getType();
      Method method = request.getWrhapiRequest().getMethod();
      if (Type.NO_RESOURCE.equals(type) && Method.OPTIONS.equals(method)) {
        ResponseBuilder builder = javax.ws.rs.core.Response.ok();
        StringWriter sw = new StringWriter();
        Iterator<HttpMethod> iter = httpMethods.iterator();
        if (iter.hasNext()) {
          for (int i = 0; i < httpMethods.size() - 1; i++) {
            HttpMethod httpMethod = iter.next();
            sw.append(httpMethod.value());
            sw.append(",");
          }
          sw.append(iter.next().value());
        }
        builder.header(HeaderName.ALLOW.toString(), sw.toString());
        methodResponse = getWildCardOptionsResponse();

      } else {
        RootResources.ResourceAndPathMatching resourceAndPathMatching;
        configLock.readLock().lock();
        try {
          resourceAndPathMatching = rootResources.getResourceAndPathMatching(request);
        } finally {
          configLock.readLock().unlock();
        }
        final PathMatching pathMatching = resourceAndPathMatching.getPathMatching();

        methodResponse = resourceExecutor.execute(
            request, resourceAndPathMatching.getRootResource(),
            pathMatching.getRemainingURIPath(), pathMatching.getParameters());
      }
      ProcessableResponse processableResponse;
      try {
        processableResponse = (ProcessableResponse) methodResponse;
      } catch (ClassCastException e) {
        throw new RuntimeException("processing of other MethodResponse" +
            " implementations not yet supported");
      }
      ResponseProcessor.handleReturnValue(request, response, processableResponse);
     
    } catch (ResourceMethodException ex) {
      Throwable cause = ex.getCause();
      if (cause == null) { // This should not happen
        logger.error("Exception {}", ex);
      } else {
        handleException(cause, request, response);
      }
    } catch (NoMatchingRootResourceException e) {
      response.setResponseStatus(ResponseStatus.NOT_FOUND);
      response.setBody(new MessageBody2Read() {

        @Override
        public ReadableByteChannel read() throws IOException {
          return Channels
              .newChannel(new ByteArrayInputStream(
                  ("JaxRsHandler - " + rootResources.size() + " root resource descriptor registered")
                      .getBytes()));
        }
      });
    } catch (Exception ex) {
      handleException(ex, request, response);
    }
  }

  static void handleException(Throwable exception, WebRequest request, Response response) throws HandlerException, RuntimeException {
    if (exception instanceof WebApplicationException) {
      WebApplicationException webEx = (WebApplicationException) exception;
      logger.debug("Exception {}", webEx);
      javax.ws.rs.core.Response jaxResponse = webEx.getResponse();
      if ((jaxResponse == null) || (jaxResponse.getEntity() == null)) {
        ExceptionMapper<WebApplicationException> exMapper = (ExceptionMapper<WebApplicationException>) providers
            .getExceptionMapper(webEx.getClass());
        if (exMapper != null) {
          jaxResponse = exMapper.toResponse(webEx);
        }
      }
      try {
        ResponseProcessor.processJaxResponse(request, response, jaxResponse,
            null, Collections.singleton(MediaType.valueOf("text/html")));
        return;
      } catch (IOException ex1) {
        logger.info("Exception processing response from WebApplicationException", ex1);
      }
    }
    ExceptionMapper exMapper = providers.getExceptionMapper(exception.getClass());
    if (exMapper != null) {
      logger.info("Exception with exception mapper", exception);
      javax.ws.rs.core.Response jaxResponse;
      try {
        jaxResponse = exMapper.toResponse(exception);
      } catch (Exception ex1) {
        /*
         * Return a server error (status code 500) response to
         * the client
         */
        jaxResponse = javax.ws.rs.core.Response.status(javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR).build();
      }
      try {
        ResponseProcessor.processJaxResponse(request, response, jaxResponse, null, Collections.singleton(MediaType.valueOf("text/html")));
      } catch (IOException ex1) {
        logger.info("Exception processing response from exception mapper", ex1);
      }
    } else {
      /*
       * Unchecked exceptions and errors MUST be re-thrown and
       * allowed to propagate to the underlying container
       */
      if (exception instanceof RuntimeException) {
        if (!(exception instanceof AccessControlException)) {
          logger.warn("RuntimeException (with no exception mapper)", exception);
        } else {
          logger.info("AccessControlException (will rethrow)", exception);
        }
        throw (RuntimeException) exception;
      }
      logger.warn("Exception (with no exception mapper)", exception);
      throw new HandlerException(exception);
    }
  }

  private MethodResponse getWildCardOptionsResponse() {
    ResponseBuilder builder = javax.ws.rs.core.Response.ok();
        StringWriter sw = new StringWriter();
        Iterator<HttpMethod> iter = httpMethods.iterator();
        if (iter.hasNext()) {
          for (int i = 0; i < httpMethods.size() - 1; i++) {
            HttpMethod httpMethod = iter.next();
            sw.append(httpMethod.value());
            sw.append(",");
          }
          sw.append(iter.next().value());
        }
        builder.header(HeaderName.ALLOW.toString(), sw.toString());
        return  ProcessableResponse.createProcessableResponse(
            builder.build(), null, null, null, null);
  }
}
TOP

Related Classes of org.apache.clerezza.triaxrs.JaxRsHandler

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.