Package org.apache.clerezza.triaxrs

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

/*
* 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 java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.nio.channels.Channels;
import java.nio.channels.WritableByteChannel;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.activation.UnsupportedDataTypeException;
import javax.security.auth.Subject;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.RuntimeDelegate;
import javax.ws.rs.ext.RuntimeDelegate.HeaderDelegate;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.clerezza.triaxrs.providers.provided.JafMessageBodyWriter;
import org.apache.clerezza.triaxrs.util.AcceptHeader;
import org.apache.clerezza.triaxrs.util.FirstByteActionOutputStream;
import org.apache.clerezza.triaxrs.util.MediaTypeComparator;
import org.apache.clerezza.triaxrs.util.BodyStoringResponse;
import org.apache.clerezza.triaxrs.util.InconsistentMediaTypeComparator;
import org.wymiwyg.wrhapi.HandlerException;
import org.wymiwyg.wrhapi.HeaderName;
import org.wymiwyg.wrhapi.MessageBody;
import org.wymiwyg.wrhapi.Response;
import org.wymiwyg.wrhapi.ResponseStatus;
import org.wymiwyg.wrhapi.util.MessageBody2Write;

/**
* @author mir
*
*/
class ResponseProcessor {

  final static private Logger logger = LoggerFactory.getLogger(ResponseProcessor.class);

  static void handleReturnValue(final WebRequest request, Response response,
      ProcessableResponse processableResponse) throws HandlerException {
    try {
      Annotation[] annotations = processableResponse.getAnnotations();
      Set<MediaType> methodProducibleMediaTypes = processableResponse.getMethodProducibleTypes();

      processJaxResponse(request, response, processableResponse, annotations,
          methodProducibleMediaTypes);
    } catch (IOException ex) {
      throw new HandlerException(ex);
    }

  }

  static void processJaxResponse(final WebRequest request,
      final Response response, javax.ws.rs.core.Response jaxResponse,
      final Annotation[] annotations,
      Set<MediaType> methodProducibleMediaTypes)
      throws HandlerException, IOException {

    Object entity = jaxResponse.getEntity();

    final MultivaluedMap<String, Object> headerMap = jaxResponse.getMetadata();

    int responseStatus = jaxResponse.getStatus();
    if (responseStatus == -1) {
      responseStatus = ResponseStatus.SUCCESS.getCode();
    }
    if (entity == null) {
      response.setHeader(HeaderName.CONTENT_LENGTH, 0);
      if (responseStatus == ResponseStatus.SUCCESS.getCode()) {
        response.setResponseStatus(ResponseStatus.NO_CONTENT);
        flushHeaders(headerMap, response);
        return;
      } else {
        if (responseStatus > 400) {
          entity = getExplanation(responseStatus);
          headerMap.putSingle(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_HTML_TYPE);
        } else {
          response.setResponseStatus(ResponseStatus.getInstanceByCode(responseStatus));
          flushHeaders(headerMap, response);
          return;
        }
      }

    }
    final Type entityType;
    if (entity instanceof GenericEntity) {
      entityType = ((GenericEntity<?>) entity).getType();
      entity = ((GenericEntity<?>) entity).getEntity();
    } else {
      entityType = null;
    }

    // get best writer
    List<MediaType> methodProducibleMediaTypesList = new ArrayList<MediaType>(
        methodProducibleMediaTypes);
    AcceptHeader acceptHeader = request.getAcceptHeader();
    if (methodProducibleMediaTypesList.size() == 0) {
      methodProducibleMediaTypesList.add(MediaType.WILDCARD_TYPE);
    }
    final List<MediaType> expandedMethodProducibleMediaTypesList = expandListWithConcreterTypesFromAccept(methodProducibleMediaTypesList, acceptHeader);
    MessageBodyWriter<Object> writer = null;
    List<Set<MediaType>> expandedMethodProducibleMediaTypeClasses
        = getSortedClasses(expandedMethodProducibleMediaTypesList,
        new InconsistentMediaTypeComparator(acceptHeader));
    Collections.sort(methodProducibleMediaTypesList,
        new MediaTypeComparator(acceptHeader));
    MediaType relevantMethodProducibleType = null;

    for (Set<MediaType> preferenceClass : expandedMethodProducibleMediaTypeClasses) {
      int lastWriterConcreteness = -1;
      for (MediaType mediaType : preferenceClass) {
        MessageBodyWriter<Object> currentWriter = (MessageBodyWriter<Object>) JaxRsHandler.providers.getMessageBodyWriter(entity.getClass(), entityType,
            annotations, mediaType);
        if (currentWriter != null) {
          int writerConcreteness = getWriterConcreteness(currentWriter, mediaType);
          if (writerConcreteness > lastWriterConcreteness) {
            for (MediaType methodMediaType : methodProducibleMediaTypesList) {
              if (methodMediaType.isCompatible(mediaType)) {
                relevantMethodProducibleType = methodMediaType;
                break;
              }
            }
            writer = currentWriter;
          }
        }
      }
      if (writer != null) {
        break;
      }
    }


    if (writer == null) {
      for (MediaType mediaType : expandedMethodProducibleMediaTypesList) {
        try {
          writer = new JafMessageBodyWriter<Object>(entity, mediaType);
        } catch (UnsupportedDataTypeException ex) {
          logger.debug("No JafMessageBodyWriter for {}", mediaType);
        }
        if (writer != null) {
          relevantMethodProducibleType = mediaType;
          break;
        }
      }
    }
    if (writer == null) {
      javax.ws.rs.core.Response r = javax.ws.rs.core.Response.status(
          Status.INTERNAL_SERVER_ERROR).entity(
          "No suitable MessageBodyWriter available").type(
          MediaType.TEXT_PLAIN_TYPE).build();
      throw new WebApplicationException(r);
    }

    // gain producible concreteness by looking at writer's @Produces
    MediaType relevantProducibleType = null;
    if ((relevantMethodProducibleType == null) || (MediaTypeComparator.countWildChars(relevantMethodProducibleType) > 0)) {

      List<MediaType> writerProducibleMediaTypes = getWriterProduces(writer);
      Collections.sort(writerProducibleMediaTypes,
          new MediaTypeComparator(acceptHeader));
      if (relevantMethodProducibleType != null) {
        Iterator<MediaType> writerProducibleMediaTypesIter = writerProducibleMediaTypes.iterator();
        while (writerProducibleMediaTypesIter.hasNext()) {
          MediaType currentProducible = writerProducibleMediaTypesIter.next();
          if (!relevantMethodProducibleType.isCompatible(currentProducible)) {
            continue;
          }
          if (MediaTypeComparator.compareByWildCardCount(
              relevantMethodProducibleType, currentProducible) == 1) {
            relevantProducibleType = currentProducible;
          } else {
            relevantProducibleType = relevantMethodProducibleType;
          }
          break;
        }
      } else {
        relevantProducibleType = writerProducibleMediaTypes.get(0);
      }
    } else {
      relevantProducibleType = relevantMethodProducibleType;
    }
    if (relevantProducibleType == null) {
      throw new RuntimeException("The relevantMethodProducibleType " + relevantMethodProducibleType + " is not compatible with any of the producible types of " + writer);
    }

    // if relevantProducibleType is not concrete get the first matching
    // concrete from accept
    MediaType mediaType;
    if (MediaTypeComparator.countWildChars(relevantProducibleType) == 0) {
      mediaType = relevantProducibleType;
    } else {
      mediaType = relevantProducibleType;
      for (MediaType acceptType : acceptHeader.getEntries()) {
        if (acceptType.isCompatible(relevantProducibleType)) {
          if (MediaTypeComparator.compareByWildCardCount(acceptType,
              mediaType) == 1) {
            mediaType = acceptType;
          }
        }
      }
    }

    if (acceptHeader.getAcceptingMediaType(mediaType).isEmpty()) {
      if (!mediaType.equals(MediaType.TEXT_HTML_TYPE)) {
        throw new WebApplicationException(406);
      }
    }

    mediaType = getConcreteMediaTypeFromPattern(mediaType);

    response.setResponseStatus(ResponseStatus.getInstanceByCode(responseStatus));

    long size = writer.getSize(entity, entity.getClass(), entityType,
        annotations, mediaType);

    if (size != -1) {
      response.setHeader(HeaderName.CONTENT_LENGTH, size);
    }

    List<Object> contentTypeList = jaxResponse.getMetadata().get(
        HttpHeaders.CONTENT_TYPE);
    if ((contentTypeList != null) && (contentTypeList.size() > 0)) {
      Object mediaTypeObject = contentTypeList.get(0);
      if (mediaTypeObject instanceof MediaType) {
        mediaType = (MediaType) mediaTypeObject;
      } else {
        String mediaTypeString = getStringValueFromHeader(mediaTypeObject);
        mediaType = MediaType.valueOf(mediaTypeString);
      }
    } else {
      headerMap.add(HttpHeaders.CONTENT_TYPE, mediaType);
    }

    final MessageBodyWriter<Object> finalWriter = writer;
    final Object finalEntity = entity;
    final MediaType finalMediaType = mediaType;
    response.setBody(new MessageBody2Write() {

      AccessControlContext context = AccessController.getContext();
      Subject subject = AccessController.doPrivileged(new PrivilegedAction<Subject>() {
        @Override
        public Subject run() {
          return Subject.getSubject(context);
        }
      });
     
      @Override
      public void writeTo(final WritableByteChannel out) throws IOException {
        final OutputStream headerFlushingOutputStream = new FirstByteActionOutputStream(
            Channels.newOutputStream(out), new Runnable() {
          // this is executed when the first character is written to
          // the stream

          @Override
          public void run() {
            try {
              setDefaultCacheControlHeader(headerMap);
              flushHeaders(headerMap, response);
            } catch (HandlerException ex) {
              logger.error("Exception {}", ex.toString(), ex);
            }
          }
        });

        if (subject != null) {
          try {
            Subject.doAs(subject, new PrivilegedExceptionAction<Object>() {

              @Override
              public Object run() throws Exception {
                writeTo(headerFlushingOutputStream, out);
                return null;
              }
            });
          } catch (PrivilegedActionException ex) {
            Throwable cause = ex.getCause();
            if (cause instanceof RuntimeException) {
              throw (RuntimeException) cause;
            } else {
              throw new RuntimeException(cause);
            }
          }
        } else {
          writeTo(headerFlushingOutputStream, out);
        }
      }

      private void writeTo(OutputStream firstByteActionOut, final WritableByteChannel out)
          throws IOException{
        try {
          finalWriter.writeTo(finalEntity, finalEntity.getClass(), entityType,
              annotations, finalMediaType, headerMap, firstByteActionOut);
          firstByteActionOut.close();
          JaxRsHandler.localRequest.remove();
        } catch (Exception ex) {
          try {
            BodyStoringResponse responseFake = new BodyStoringResponse(response);
            JaxRsHandler.handleException(ex, request, responseFake);
            final MessageBody body = responseFake.getBody();
            if (body != null) {
              try {
                //doing priviledged as this might invoke doAs to
                //write the body as subject
                AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {

                  @Override
                  public Subject run() throws IOException {
                    body.writeTo(out);
                    return null;
                  }
                });
              } catch (PrivilegedActionException privEx) {
                Throwable cause = privEx.getCause();
                if (cause instanceof IOException) {
                  throw (IOException)cause;
                }
                if (cause instanceof RuntimeException) {
                  throw (RuntimeException)cause;
                }
                if (cause instanceof Error) {
                  throw (Error)cause;
                }
                throw new RuntimeException(cause);
              }
             
            }
          } catch (HandlerException e) {
            throw new RuntimeException(e);
          }
        }
      }
    });
  }

  private static List<MediaType> expandListWithConcreterTypesFromAccept(List<MediaType> mediaTypesList, AcceptHeader acceptHeader) {
    Collection<MediaType> addition = new HashSet<MediaType>();
    for (MediaType mediaType : mediaTypesList) {
      for (MediaType acceptingType : acceptHeader.getAcceptingMediaType(mediaType)) {
        if (MediaTypeComparator.compareByWildCardCount(acceptingType, mediaType) == -1) {
          addition.add(acceptingType);
        }
      }
    }
    final List<MediaType> result = new ArrayList<MediaType>();
    result.addAll(mediaTypesList);
    result.addAll(addition);
    return result;
  }

  private static String getExplanation(int responseStatus) {
    StringWriter stringWriter = new StringWriter();
    PrintWriter out = new PrintWriter(stringWriter);
    out.println("<html>");
    out.println("<head>");
    out.print("<title>");
    out.print("Error: ");
    out.print(responseStatus);
    out.println("</title>");
    out.print("<body>");
    if (responseStatus >= 500) {
      writeServerError(out, responseStatus);
    } else {
      writeClientError(out, responseStatus);
    }
    out.print("</body>");
    out.println("</head>");
    out.println("</html>");
    return stringWriter.toString();
  }

  private static void writeServerError(PrintWriter out, int responseStatus) {
    out.println("<h1>Server error</h1>");
    out.println("<p>An error occurred and the server was unable to process your " +
        "request. We apologize for the inconveniences</p>");

  }

  private static void writeClientError(PrintWriter out, int responseStatus) {
    if (responseStatus == 404) {
      out.println("<h1>Not found</h1>");
      out.println("<p>The requested resource does not exist on this server. " +
          "Please check the entered address (URI). If you followed a link " +
          "please inform the creator of the page containing the link.</p>");
    } else {
      out.println("<h1>Client error</h1>");
      out.println("<p>The server was unable to process your request. This might " +
          "be caused by properties of your request incompatible with the " +
          "capacities of the server for the requested resource.</p>");
    }
  }

  private static List<MediaType> getWriterProduces(MessageBodyWriter<?> writer) {
    List<MediaType> result = new ArrayList<MediaType>();
    Produces writerProduces = writer.getClass().getAnnotation(
        Produces.class);
    if (writerProduces != null) {
      for (String produced : writerProduces.value()) {
        MediaType producibleType = MediaType.valueOf(produced);
        result.add(producibleType);
      }
    } else {
      result.add(MediaType.WILDCARD_TYPE);
    }
    return result;
  }

  private static MessageBodyWriter<Object> getJafMethodBodyWriter(
      Object entity, List<MediaType> methodProducibleMediaTypesList)
      throws IOException {
    MediaType mediaType;
    if (methodProducibleMediaTypesList.size() > 0) {
      mediaType = methodProducibleMediaTypesList.get(0);
    } else {
      mediaType = MediaType.WILDCARD_TYPE;
    }
    try {
      return new JafMessageBodyWriter<Object>(entity, mediaType);
    } catch (UnsupportedDataTypeException ex) {
      javax.ws.rs.core.Response r = javax.ws.rs.core.Response.status(
          Status.INTERNAL_SERVER_ERROR).entity(
          "No suitable MessageBodyWriter available").type(
          MediaType.TEXT_PLAIN_TYPE).build();
      throw new WebApplicationException(r);
    }
  }

  private static void flushHeaders(MultivaluedMap<String, Object> headerMap,
      Response response) throws HandlerException {
    for (String headerNameString : headerMap.keySet()) {
      List<Object> values = headerMap.get(headerNameString);
      for (Object object : values) {
        final String stringValue = getStringValueFromHeader(object);
        response.setHeader(HeaderName.get(headerNameString),
            stringValue);
      }
    }
  }

  private static <T> String getStringValueFromHeader(T headerObject) {
    HeaderDelegate<T> headerDelegate = RuntimeDelegate.getInstance().createHeaderDelegate((Class<T>)headerObject.getClass());
    String mediaTypeString = headerDelegate != null ? headerDelegate.toString(headerObject) : headerObject.toString();
    return mediaTypeString;
  }

  private static MediaType getConcreteMediaTypeFromPattern(
      final MediaType mediaType) {
    if (mediaType.isWildcardSubtype()) {
      return MediaType.APPLICATION_OCTET_STREAM_TYPE;
    }
    Map<String, String> params = mediaType.getParameters();
    if (!params.containsKey("q")) {
      return mediaType;
    }
    Map<String, String> resultParams = new HashMap<String, String>();
    for (Map.Entry<String, String> entry : params.entrySet()) {
      final String key = entry.getKey();
      if (!key.equals("q")) {
        resultParams.put(key, entry.getValue());
      }
    }
    return new MediaType(mediaType.getType(), mediaType.getSubtype(),
        resultParams);
  }

  /**
   *
   * @param <T>
   * @param collection
   * @param comparator
   * @return a list containing sets of instances for which the comparator returns 0
   * in oder
   */
  private static <T> List<Set<T>> getSortedClasses(Collection<T> collection,
      Comparator<T> comparator) {
    List<Set<T>> result = new ArrayList<Set<T>>();
    if (collection.size() > 0) {
      Set<T> pivotSet = new HashSet<T>();
      Collection<T> before = new ArrayList<T>();
      Collection<T> after = new ArrayList<T>();
      Iterator<T> iterator = collection.iterator();
      T pivot = iterator.next();
      pivotSet.add(pivot);
      while (iterator.hasNext()) {
        T next = iterator.next();
        int comparison = comparator.compare(next, pivot);
        if (comparison > 0) {
          after.add(next);
        } else {
          if (comparison < 0) {
            before.add(next);
          } else {
            pivotSet.add(next);
          }
        }
      }
      result.addAll(getSortedClasses(before, comparator));
      result.add(pivotSet);
      result.addAll(getSortedClasses(after, comparator));
    }
    return result;

  }

  /**
   *
   * @param writer
   * @param mediaType
   * @return 0 is mediaType ismatched only by wildcard in the @Produces of writer,
   *   1, if the supertype is concrete, 2 if the subtype is concrete too
   */
  private static int getWriterConcreteness(MessageBodyWriter<Object> writer, MediaType mediaType) {
    Produces produces = writer.getClass().getAnnotation(Produces.class);
    int result = 0;
    if (produces != null) {
      for (String producedValue : produces.value()) {
        MediaType producesType = MediaType.valueOf(producedValue);
        if (producesType.isCompatible(mediaType)) {
          int concreteness = 2 - MediaTypeComparator.countWildChars(producesType);
          if (concreteness > result) {
            result = concreteness;
          }
        }
      }
    }
    return result;
  }

  private static void setDefaultCacheControlHeader(MultivaluedMap<String, Object> headerMap) {
    if (headerMap.containsKey(HeaderName.CACHE_CONTROL.toString()) ||
        headerMap.containsKey(HeaderName.EXPIRES.toString()) ||
        headerMap.containsKey(HeaderName.PRAGMA.toString()) ||
        headerMap.containsKey(HeaderName.LAST_MODIFIED.toString()) ||
        headerMap.containsKey("ETag")) {
      return;
    }
    headerMap.putSingle(HeaderName.CACHE_CONTROL.toString(), "no-cache");
  }
}
TOP

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

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.