Added:
release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/CallMethod.java
==============================================================================
---
release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/CallMethod.java
(added)
+++
release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/CallMethod.java
Fri Sep 8 23:25:34 2017
@@ -0,0 +1,948 @@
+//
***************************************************************************************************************************
+// * 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.juneau.rest;
+
+import static javax.servlet.http.HttpServletResponse.*;
+import static org.apache.juneau.dto.swagger.SwaggerBuilder.*;
+import static org.apache.juneau.internal.ClassUtils.*;
+import static org.apache.juneau.internal.StringUtils.*;
+import static org.apache.juneau.internal.Utils.*;
+import static org.apache.juneau.rest.RestContext.*;
+import static org.apache.juneau.rest.RestUtils.*;
+import static org.apache.juneau.rest.annotation.Inherit.*;
+
+import java.lang.annotation.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+import javax.servlet.http.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.dto.swagger.*;
+import org.apache.juneau.encoders.*;
+import org.apache.juneau.html.*;
+import org.apache.juneau.internal.*;
+import org.apache.juneau.json.*;
+import org.apache.juneau.parser.*;
+import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.widget.*;
+import org.apache.juneau.serializer.*;
+import org.apache.juneau.svl.*;
+import org.apache.juneau.urlencoding.*;
+
+/**
+ * Represents a single Java servlet/resource method annotated with {@link
RestMethod @RestMethod}.
+ */
+@SuppressWarnings("hiding")
+class CallMethod implements Comparable<CallMethod> {
+ private final java.lang.reflect.Method method;
+ private final String httpMethod;
+ private final UrlPathPattern pathPattern;
+ private final RestParam[] params;
+ private final RestGuard[] guards;
+ private final RestMatcher[] optionalMatchers;
+ private final RestMatcher[] requiredMatchers;
+ private final RestConverter[] converters;
+ private final SerializerGroup serializers;
+ private final ParserGroup parsers;
+ private final EncoderGroup encoders;
+ private final UrlEncodingParser urlEncodingParser;
+ private final UrlEncodingSerializer urlEncodingSerializer;
+ private final ObjectMap properties;
+ private final Map<String,String> defaultRequestHeaders, defaultQuery,
defaultFormData;
+ private final String defaultCharset;
+ private final boolean deprecated;
+ private final String description, tags, summary, externalDocs;
+ private final Integer priority;
+ private final org.apache.juneau.rest.annotation.Parameter[] parameters;
+ private final Response[] responses;
+ private final RestContext context;
+ private final BeanContext beanContext;
+ final String htmlHeader, htmlNav, htmlAside, htmlFooter, htmlStyle,
htmlStylesheet, htmlScript, htmlNoResultsMessage;
+ final String[] htmlLinks;
+ final boolean htmlNoWrap;
+ final HtmlDocTemplate htmlTemplate;
+ private final Map<String,Widget> widgets;
+
+ CallMethod(Object servlet, java.lang.reflect.Method method, RestContext
context) throws RestServletException {
+ Builder b = new Builder(servlet, method, context);
+ this.context = context;
+ this.method = method;
+ this.httpMethod = b.httpMethod;
+ this.pathPattern = b.pathPattern;
+ this.params = b.params;
+ this.guards = b.guards;
+ this.optionalMatchers = b.optionalMatchers;
+ this.requiredMatchers = b.requiredMatchers;
+ this.converters = b.converters;
+ this.serializers = b.serializers;
+ this.parsers = b.parsers;
+ this.encoders = b.encoders;
+ this.urlEncodingParser = b.urlEncodingParser;
+ this.urlEncodingSerializer = b.urlEncodingSerializer;
+ this.beanContext = b.beanContext;
+ this.properties = b.properties;
+ this.defaultRequestHeaders = b.defaultRequestHeaders;
+ this.defaultQuery = b.defaultQuery;
+ this.defaultFormData = b.defaultFormData;
+ this.defaultCharset = b.defaultCharset;
+ this.deprecated = b.deprecated;
+ this.description = b.description;
+ this.tags = b.tags;
+ this.summary = b.summary;
+ this.externalDocs = b.externalDocs;
+ this.priority = b.priority;
+ this.parameters = b.parameters;
+ this.responses = b.responses;
+ this.htmlHeader = b.htmlHeader;
+ this.htmlLinks = b.htmlLinks;
+ this.htmlNav = b.htmlNav;
+ this.htmlAside = b.htmlAside;
+ this.htmlFooter = b.htmlFooter;
+ this.htmlStyle = b.htmlStyle;
+ this.htmlStylesheet = b.htmlStylesheet;
+ this.htmlScript = b.htmlScript;
+ this.htmlNoWrap = b.htmlNoWrap;
+ this.htmlTemplate = b.htmlTemplate;
+ this.htmlNoResultsMessage = b.htmlNoResultsMessage;
+ this.widgets = Collections.unmodifiableMap(b.htmlWidgets);
+ }
+
+ private static class Builder {
+ private String httpMethod, defaultCharset, description, tags,
summary, externalDocs, htmlNav, htmlAside,
+ htmlFooter, htmlStyle, htmlStylesheet, htmlScript,
htmlHeader, htmlNoResultsMessage;
+ private String[] htmlLinks;
+ private boolean htmlNoWrap;
+ private HtmlDocTemplate htmlTemplate;
+ private UrlPathPattern pathPattern;
+ private RestParam[] params;
+ private RestGuard[] guards;
+ private RestMatcher[] optionalMatchers, requiredMatchers;
+ private RestConverter[] converters;
+ private SerializerGroup serializers;
+ private ParserGroup parsers;
+ private EncoderGroup encoders;
+ private UrlEncodingParser urlEncodingParser;
+ private UrlEncodingSerializer urlEncodingSerializer;
+ private BeanContext beanContext;
+ private ObjectMap properties;
+ private Map<String,String> defaultRequestHeaders, defaultQuery,
defaultFormData;
+ private boolean plainParams, deprecated;
+ private Integer priority;
+ private org.apache.juneau.rest.annotation.Parameter[]
parameters;
+ private Response[] responses;
+ private Map<String,Widget> htmlWidgets;
+
+ private Builder(Object servlet, java.lang.reflect.Method
method, RestContext context) throws RestServletException {
+ String sig = method.getDeclaringClass().getName() + '.'
+ method.getName();
+
+ try {
+
+ RestMethod m =
method.getAnnotation(RestMethod.class);
+ if (m == null)
+ throw new
RestServletException("@RestMethod annotation not found on method ''{0}''", sig);
+
+ if (! m.description().isEmpty())
+ description = m.description();
+ MethodSwagger sm = m.swagger();
+ if (! sm.tags().isEmpty())
+ tags = sm.tags();
+ if (! m.summary().isEmpty())
+ summary = m.summary();
+ if (! sm.externalDocs().isEmpty())
+ externalDocs = sm.externalDocs();
+ deprecated = sm.deprecated();
+ parameters = sm.parameters();
+ responses = sm.responses();
+ serializers = context.getSerializers();
+ parsers = context.getParsers();
+ urlEncodingSerializer =
context.getUrlEncodingSerializer();
+ urlEncodingParser =
context.getUrlEncodingParser();
+ beanContext = context.getBeanContext();
+ encoders = context.getEncoders();
+ properties = context.getProperties();
+
+ HtmlDoc hd = m.htmldoc();
+ htmlWidgets = new
HashMap<String,Widget>(context.getHtmlWidgets());
+ for (Class<? extends Widget> wc : hd.widgets())
{
+ Widget w =
ClassUtils.newInstance(Widget.class, wc);
+ htmlWidgets.put(w.getName(), w);
+ }
+
+ htmlHeader =
resolveNewlineSeparatedAnnotation(hd.header(), context.getHtmlHeader());
+ htmlNav =
resolveNewlineSeparatedAnnotation(hd.nav(), context.getHtmlNav());
+ htmlAside =
resolveNewlineSeparatedAnnotation(hd.aside(), context.getHtmlAside());
+ htmlFooter =
resolveNewlineSeparatedAnnotation(hd.footer(), context.getHtmlFooter());
+ htmlStyle =
resolveNewlineSeparatedAnnotation(hd.style(), context.getHtmlStyle());
+ htmlScript =
resolveNewlineSeparatedAnnotation(hd.script(), context.getHtmlScript());
+ htmlLinks = resolveLinks(hd.links(),
context.getHtmlLinks());
+ htmlStylesheet = hd.stylesheet().isEmpty() ?
context.getHtmlStylesheet() : hd.stylesheet();
+ htmlNoWrap = hd.nowrap() ? hd.nowrap() :
context.getHtmlNoWrap();
+ htmlNoResultsMessage =
hd.noResultsMessage().isEmpty() ? context.getHtmlNoResultsMessage() :
hd.noResultsMessage();
+ htmlTemplate =
+ hd.template() == HtmlDocTemplate.class
+ ? context.getHtmlTemplate()
+ :
ClassUtils.newInstance(HtmlDocTemplate.class, hd.template());
+
+ List<Inherit> si =
Arrays.asList(m.serializersInherit());
+ List<Inherit> pi =
Arrays.asList(m.parsersInherit());
+
+ SerializerGroupBuilder sgb = null;
+ ParserGroupBuilder pgb = null;
+ UrlEncodingParserBuilder uepb = null;
+
+ if (m.serializers().length > 0 ||
m.parsers().length > 0 || m.properties().length > 0 || m.flags().length > 0
+ || m.beanFilters().length > 0
|| m.pojoSwaps().length > 0 || m.bpi().length > 0
+ || m.bpx().length > 0) {
+ sgb = new SerializerGroupBuilder();
+ pgb = new ParserGroupBuilder();
+ uepb = new
UrlEncodingParserBuilder(urlEncodingParser.createPropertyStore());
+
+ if (si.contains(SERIALIZERS) ||
m.serializers().length == 0)
+
sgb.append(serializers.getSerializers());
+
+ if (pi.contains(PARSERS) ||
m.parsers().length == 0)
+
pgb.append(parsers.getParsers());
+ }
+
+ httpMethod =
m.name().toUpperCase(Locale.ENGLISH);
+ if (httpMethod.equals("") &&
method.getName().startsWith("do"))
+ httpMethod =
method.getName().substring(2).toUpperCase(Locale.ENGLISH);
+ if (httpMethod.equals(""))
+ httpMethod = "GET";
+ if (httpMethod.equals("METHOD"))
+ httpMethod = "*";
+
+ priority = m.priority();
+
+ String p = m.path();
+ converters = new
RestConverter[m.converters().length];
+ for (int i = 0; i < converters.length; i++)
+ converters[i] =
newInstance(RestConverter.class, m.converters()[i]);
+
+ guards = new RestGuard[m.guards().length];
+ for (int i = 0; i < guards.length; i++)
+ guards[i] =
newInstance(RestGuard.class, m.guards()[i]);
+
+ List<RestMatcher> optionalMatchers = new
LinkedList<RestMatcher>(), requiredMatchers = new LinkedList<RestMatcher>();
+ for (int i = 0; i < m.matchers().length; i++) {
+ Class<? extends RestMatcher> c =
m.matchers()[i];
+ RestMatcher matcher = null;
+ if
(isParentClass(RestMatcherReflecting.class, c))
+ matcher =
newInstance(RestMatcherReflecting.class, c, servlet, method);
+ else
+ matcher =
newInstance(RestMatcher.class, c);
+ if (matcher.mustMatch())
+ requiredMatchers.add(matcher);
+ else
+ optionalMatchers.add(matcher);
+ }
+ if (! m.clientVersion().isEmpty())
+ requiredMatchers.add(new
ClientVersionMatcher(context.getClientVersionHeader(), method));
+
+ this.requiredMatchers =
requiredMatchers.toArray(new RestMatcher[requiredMatchers.size()]);
+ this.optionalMatchers =
optionalMatchers.toArray(new RestMatcher[optionalMatchers.size()]);
+
+ Class<?>[] beanFilters =
context.getBeanFilters(), pojoSwaps = context.getPojoSwaps();
+
+ if (sgb != null) {
+ sgb.append(m.serializers());
+ if (si.contains(TRANSFORMS))
+
sgb.beanFilters(beanFilters).pojoSwaps(pojoSwaps);
+ if (si.contains(PROPERTIES))
+ sgb.properties(properties);
+ for (Property p1 : m.properties())
+ sgb.property(p1.name(),
p1.value());
+ for (String p1 : m.flags())
+ sgb.property(p1, true);
+ if (m.bpi().length > 0) {
+ Map<String,String> bpiMap = new
LinkedHashMap<String,String>();
+ for (String s : m.bpi()) {
+ for (String s2 :
split(s, ';')) {
+ int i =
s2.indexOf(':');
+ if (i == -1)
+ throw
new RestServletException(
+
"Invalid format for @RestMethod.bpi() on method ''{0}''. Must be in the format
\"ClassName: comma-delimited-tokens\". \nValue: {1}", sig, s);
+
bpiMap.put(s2.substring(0, i).trim(), s2.substring(i+1).trim());
+ }
+ }
+ sgb.includeProperties(bpiMap);
+ }
+ if (m.bpx().length > 0) {
+ Map<String,String> bpxMap = new
LinkedHashMap<String,String>();
+ for (String s : m.bpx()) {
+ for (String s2 :
split(s, ';')) {
+ int i =
s2.indexOf(':');
+ if (i == -1)
+ throw
new RestServletException(
+
"Invalid format for @RestMethod.bpx() on method ''{0}''. Must be in the format
\"ClassName: comma-delimited-tokens\". \nValue: {1}", sig, s);
+
bpxMap.put(s2.substring(0, i).trim(), s2.substring(i+1).trim());
+ }
+ }
+ sgb.excludeProperties(bpxMap);
+ }
+ sgb.beanFilters(m.beanFilters());
+ sgb.pojoSwaps(m.pojoSwaps());
+ }
+
+ if (pgb != null) {
+ pgb.append(m.parsers());
+ if (pi.contains(TRANSFORMS))
+
pgb.beanFilters(beanFilters).pojoSwaps(pojoSwaps);
+ if (pi.contains(PROPERTIES))
+ pgb.properties(properties);
+ for (Property p1 : m.properties())
+ pgb.property(p1.name(),
p1.value());
+ for (String p1 : m.flags())
+ pgb.property(p1, true);
+ pgb.beanFilters(m.beanFilters());
+ pgb.pojoSwaps(m.pojoSwaps());
+ }
+
+ if (uepb != null) {
+ for (Property p1 : m.properties())
+ uepb.property(p1.name(),
p1.value());
+ for (String p1 : m.flags())
+ uepb.property(p1, true);
+ uepb.beanFilters(m.beanFilters());
+ uepb.pojoSwaps(m.pojoSwaps());
+ }
+
+ if (m.properties().length > 0 ||
m.flags().length > 0) {
+ properties = new
ObjectMap().setInner(properties);
+ for (Property p1 : m.properties())
+ properties.put(p1.name(),
p1.value());
+ for (String p1 : m.flags())
+ properties.put(p1, true);
+ }
+
+ if (m.encoders().length > 0 || !
m.inheritEncoders()) {
+ EncoderGroupBuilder g = new
EncoderGroupBuilder();
+ if (m.inheritEncoders())
+ g.append(encoders);
+ else
+
g.append(IdentityEncoder.INSTANCE);
+
+ for (Class<? extends Encoder> c :
m.encoders()) {
+ try {
+ g.append(c);
+ } catch (Exception e) {
+ throw new
RestServletException(
+ "Exception
occurred while trying to instantiate Encoder on method ''{0}'': ''{1}''", sig,
c.getSimpleName()).initCause(e);
+ }
+ }
+ encoders = g.build();
+ }
+
+ defaultRequestHeaders = new
TreeMap<String,String>(String.CASE_INSENSITIVE_ORDER);
+ for (String s : m.defaultRequestHeaders()) {
+ String[] h =
RestUtils.parseKeyValuePair(s);
+ if (h == null)
+ throw new RestServletException(
+ "Invalid default
request header specified on method ''{0}'': ''{1}''. Must be in the format:
''name[:=]value''", sig, s);
+ defaultRequestHeaders.put(h[0], h[1]);
+ }
+
+ defaultQuery = new
LinkedHashMap<String,String>();
+ for (String s : m.defaultQuery()) {
+ String[] h =
RestUtils.parseKeyValuePair(s);
+ if (h == null)
+ throw new RestServletException(
+ "Invalid default query
parameter specified on method ''{0}'': ''{1}''. Must be in the format:
''name[:=]value''", sig, s);
+ defaultQuery.put(h[0], h[1]);
+ }
+
+ defaultFormData = new
LinkedHashMap<String,String>();
+ for (String s : m.defaultFormData()) {
+ String[] h =
RestUtils.parseKeyValuePair(s);
+ if (h == null)
+ throw new RestServletException(
+ "Invalid default form
data parameter specified on method ''{0}'': ''{1}''. Must be in the format:
''name[:=]value''", sig, s);
+ defaultFormData.put(h[0], h[1]);
+ }
+
+ Type[] pt = method.getGenericParameterTypes();
+ Annotation[][] pa =
method.getParameterAnnotations();
+ for (int i = 0; i < pt.length; i++) {
+ for (Annotation a : pa[i]) {
+ if (a instanceof Header) {
+ Header h = (Header)a;
+ if (! h.def().isEmpty())
+
defaultRequestHeaders.put(firstNonEmpty(h.name(), h.value()), h.def());
+ } else if (a instanceof Query) {
+ Query q = (Query)a;
+ if (! q.def().isEmpty())
+
defaultQuery.put(firstNonEmpty(q.name(), q.value()), q.def());
+ } else if (a instanceof
FormData) {
+ FormData f =
(FormData)a;
+ if (! f.def().isEmpty())
+
defaultFormData.put(firstNonEmpty(f.name(), f.value()), f.def());
+ }
+ }
+ }
+
+ defaultCharset =
properties.getString(REST_defaultCharset, context.getDefaultCharset());
+ String paramFormat =
properties.getString(REST_paramFormat, context.getParamFormat());
+ plainParams = paramFormat.equals("PLAIN");
+
+ pathPattern = new UrlPathPattern(p);
+
+ params = context.findParams(method,
plainParams, pathPattern, false);
+
+ if (sgb != null) {
+ serializers = sgb.build();
+ beanContext =
serializers.getBeanContext();
+ }
+ if (pgb != null)
+ parsers = pgb.build();
+ if (uepb != null)
+ urlEncodingParser = uepb.build();
+
+ // Need this to access methods in anonymous
inner classes.
+ method.setAccessible(true);
+ } catch (RestServletException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new RestServletException("Exception
occurred while initializing method ''{0}''", sig).initCause(e);
+ }
+ }
+ }
+
+ /**
+ * Returns <jk>true</jk> if this Java method has any guards or matchers.
+ */
+ boolean hasGuardsOrMatchers() {
+ return (guards.length != 0 || requiredMatchers.length != 0 ||
optionalMatchers.length != 0);
+ }
+
+ /**
+ * Returns the HTTP method name (e.g. <js>"GET"</js>).
+ */
+ String getHttpMethod() {
+ return httpMethod;
+ }
+
+ /**
+ * Returns the path pattern for this method.
+ */
+ String getPathPattern() {
+ return pathPattern.toString();
+ }
+
+ /**
+ * Returns the localized Swagger for this Java method.
+ */
+ Operation getSwaggerOperation(RestRequest req) throws ParseException {
+ Operation o = operation()
+ .operationId(method.getName())
+ .description(getDescription(req))
+ .tags(getTags(req))
+ .summary(getSummary(req))
+ .externalDocs(getExternalDocs(req))
+ .parameters(getParameters(req))
+ .responses(getResponses(req));
+
+ if (isDeprecated())
+ o.deprecated(true);
+
+ if (!
parsers.getSupportedMediaTypes().equals(context.getParsers().getSupportedMediaTypes()))
+ o.consumes(parsers.getSupportedMediaTypes());
+
+ if (!
serializers.getSupportedMediaTypes().equals(context.getSerializers().getSupportedMediaTypes()))
+ o.produces(serializers.getSupportedMediaTypes());
+
+ return o;
+ }
+
+ private Operation getSwaggerOperationFromFile(RestRequest req) {
+ Swagger s = req.getSwaggerFromFile();
+ if (s != null && s.getPaths() != null &&
s.getPaths().get(pathPattern.getPatternString()) != null)
+ return
s.getPaths().get(pathPattern.getPatternString()).get(httpMethod);
+ return null;
+ }
+
+ /**
+ * Returns the localized summary for this Java method.
+ */
+ String getSummary(RestRequest req) {
+ VarResolverSession vr = req.getVarResolverSession();
+ if (summary != null)
+ return vr.resolve(summary);
+ String summary =
context.getMessages().findFirstString(req.getLocale(), method.getName() +
".summary");
+ if (summary != null)
+ return vr.resolve(summary);
+ Operation o = getSwaggerOperationFromFile(req);
+ if (o != null)
+ return o.getSummary();
+ return null;
+ }
+
+ /**
+ * Returns the localized description for this Java method.
+ */
+ String getDescription(RestRequest req) {
+ VarResolverSession vr = req.getVarResolverSession();
+ if (description != null)
+ return vr.resolve(description);
+ String description =
context.getMessages().findFirstString(req.getLocale(), method.getName() +
".description");
+ if (description != null)
+ return vr.resolve(description);
+ Operation o = getSwaggerOperationFromFile(req);
+ if (o != null)
+ return o.getDescription();
+ return null;
+ }
+
+ /**
+ * Returns the localized Swagger tags for this Java method.
+ */
+ private List<String> getTags(RestRequest req) {
+ VarResolverSession vr = req.getVarResolverSession();
+ JsonParser jp = JsonParser.DEFAULT;
+ try {
+ if (tags != null)
+ return jp.parse(vr.resolve(tags),
ArrayList.class, String.class);
+ String tags =
context.getMessages().findFirstString(req.getLocale(), method.getName() +
".tags");
+ if (tags != null)
+ return jp.parse(vr.resolve(tags),
ArrayList.class, String.class);
+ Operation o = getSwaggerOperationFromFile(req);
+ if (o != null)
+ return o.getTags();
+ return null;
+ } catch (Exception e) {
+ throw new RestException(SC_INTERNAL_SERVER_ERROR, e);
+ }
+ }
+
+ /**
+ * Returns the localized Swagger external docs for this Java method.
+ */
+ private ExternalDocumentation getExternalDocs(RestRequest req) {
+ VarResolverSession vr = req.getVarResolverSession();
+ JsonParser jp = JsonParser.DEFAULT;
+ try {
+ if (externalDocs != null)
+ return jp.parse(vr.resolve(externalDocs),
ExternalDocumentation.class);
+ String externalDocs =
context.getMessages().findFirstString(req.getLocale(), method.getName() +
".externalDocs");
+ if (externalDocs != null)
+ return jp.parse(vr.resolve(externalDocs),
ExternalDocumentation.class);
+ Operation o = getSwaggerOperationFromFile(req);
+ if (o != null)
+ return o.getExternalDocs();
+ return null;
+ } catch (Exception e) {
+ throw new RestException(SC_INTERNAL_SERVER_ERROR, e);
+ }
+ }
+
+ /**
+ * Returns the Swagger deprecated flag for this Java method.
+ */
+ private boolean isDeprecated() {
+ return deprecated;
+ }
+
+ /**
+ * Returns the localized Swagger parameter information for this Java
method.
+ */
+ private List<ParameterInfo> getParameters(RestRequest req) throws
ParseException {
+ Operation o = getSwaggerOperationFromFile(req);
+ if (o != null && o.getParameters() != null)
+ return o.getParameters();
+
+ VarResolverSession vr = req.getVarResolverSession();
+ JsonParser jp = JsonParser.DEFAULT;
+ Map<String,ParameterInfo> m = new
TreeMap<String,ParameterInfo>();
+
+ // First parse @RestMethod.parameters() annotation.
+ for (org.apache.juneau.rest.annotation.Parameter v :
parameters) {
+ String in = vr.resolve(v.in());
+ ParameterInfo p = parameterInfo(in,
vr.resolve(v.name()));
+
+ if (! v.description().isEmpty())
+ p.description(vr.resolve(v.description()));
+ if (v.required())
+ p.required(v.required());
+
+ if ("body".equals(in)) {
+ if (! v.schema().isEmpty())
+
p.schema(jp.parse(vr.resolve(v.schema()), SchemaInfo.class));
+ } else {
+ if (v.allowEmptyValue())
+ p.allowEmptyValue(v.allowEmptyValue());
+ if (! v.collectionFormat().isEmpty())
+
p.collectionFormat(vr.resolve(v.collectionFormat()));
+ if (! v._default().isEmpty())
+ p._default(vr.resolve(v._default()));
+ if (! v.format().isEmpty())
+ p.format(vr.resolve(v.format()));
+ if (! v.items().isEmpty())
+ p.items(jp.parse(vr.resolve(v.items()),
Items.class));
+ p.type(vr.resolve(v.type()));
+ }
+ m.put(p.getIn() + '.' + p.getName(), p);
+ }
+
+ // Next, look in resource bundle.
+ String prefix = method.getName() + ".req";
+ for (String key : context.getMessages().keySet(prefix)) {
+ if (key.length() > prefix.length()) {
+ String value =
vr.resolve(context.getMessages().getString(key));
+ String[] parts = key.substring(prefix.length()
+ 1).split("\\.");
+ String in = parts[0], name, field;
+ boolean isBody = "body".equals(in);
+ if (parts.length == (isBody ? 2 : 3)) {
+ if ("body".equals(in)) {
+ name = null;
+ field = parts[1];
+ } else {
+ name = parts[1];
+ field = parts[2];
+ }
+ String k2 = in + '.' + name;
+ ParameterInfo p = m.get(k2);
+ if (p == null) {
+ p = parameterInfoStrict(in,
name);
+ m.put(k2, p);
+ }
+
+ if (field.equals("description"))
+ p.description(value);
+ else if (field.equals("required"))
+
p.required(Boolean.valueOf(value));
+
+ if ("body".equals(in)) {
+ if (field.equals("schema"))
+
p.schema(jp.parse(value, SchemaInfo.class));
+ } else {
+ if
(field.equals("allowEmptyValue"))
+
p.allowEmptyValue(Boolean.valueOf(value));
+ else if
(field.equals("collectionFormat"))
+
p.collectionFormat(value);
+ else if
(field.equals("default"))
+ p._default(value);
+ else if (field.equals("format"))
+ p.format(value);
+ else if (field.equals("items"))
+ p.items(jp.parse(value,
Items.class));
+ else if (field.equals("type"))
+ p.type(value);
+ }
+ } else {
+ System.err.println("Unknown bundle key
'"+key+"'");
+ }
+ }
+ }
+
+ // Finally, look for parameters defined on method.
+ for (RestParam mp : this.params) {
+ RestParamType in = mp.getParamType();
+ if (in != RestParamType.OTHER) {
+ String k2 = in.toString() + '.' + (in ==
RestParamType.BODY ? null : mp.getName());
+ ParameterInfo p = m.get(k2);
+ if (p == null) {
+ p = parameterInfoStrict(in.toString(),
mp.getName());
+ m.put(k2, p);
+ }
+ }
+ }
+
+ if (m.isEmpty())
+ return null;
+ return new ArrayList<ParameterInfo>(m.values());
+ }
+
+ /**
+ * Returns the localized Swagger response information about this Java
method.
+ */
+ @SuppressWarnings("unchecked")
+ private Map<Integer,ResponseInfo> getResponses(RestRequest req) throws
ParseException {
+ Operation o = getSwaggerOperationFromFile(req);
+ if (o != null && o.getResponses() != null)
+ return o.getResponses();
+
+ VarResolverSession vr = req.getVarResolverSession();
+ JsonParser jp = JsonParser.DEFAULT;
+ Map<Integer,ResponseInfo> m = new
TreeMap<Integer,ResponseInfo>();
+ Map<String,HeaderInfo> m2 = new TreeMap<String,HeaderInfo>();
+
+ // First parse @RestMethod.parameters() annotation.
+ for (Response r : responses) {
+ int httpCode = r.value();
+ String description = r.description().isEmpty() ?
RestUtils.getHttpResponseText(r.value()) : vr.resolve(r.description());
+ ResponseInfo r2 = responseInfo(description);
+
+ if (r.headers().length > 0) {
+ for
(org.apache.juneau.rest.annotation.Parameter v : r.headers()) {
+ HeaderInfo h =
headerInfoStrict(vr.resolve(v.type()));
+ if (! v.collectionFormat().isEmpty())
+
h.collectionFormat(vr.resolve(v.collectionFormat()));
+ if (! v._default().isEmpty())
+
h._default(vr.resolve(v._default()));
+ if (! v.description().isEmpty())
+
h.description(vr.resolve(v.description()));
+ if (! v.format().isEmpty())
+
h.format(vr.resolve(v.format()));
+ if (! v.items().isEmpty())
+
h.items(jp.parse(vr.resolve(v.items()), Items.class));
+ r2.header(v.name(), h);
+ m2.put(httpCode + '.' + v.name(), h);
+ }
+ }
+ m.put(httpCode, r2);
+ }
+
+ // Next, look in resource bundle.
+ String prefix = method.getName() + ".res";
+ for (String key : context.getMessages().keySet(prefix)) {
+ if (key.length() > prefix.length()) {
+ String value =
vr.resolve(context.getMessages().getString(key));
+ String[] parts = key.substring(prefix.length()
+ 1).split("\\.");
+ int httpCode = Integer.parseInt(parts[0]);
+ ResponseInfo r2 = m.get(httpCode);
+ if (r2 == null) {
+ r2 = responseInfo(null);
+ m.put(httpCode, r2);
+ }
+
+ String name = parts.length > 1 ? parts[1] : "";
+
+ if ("header".equals(name) && parts.length > 3) {
+ String headerName = parts[2];
+ String field = parts[3];
+
+ String k2 = httpCode + '.' + headerName;
+ HeaderInfo h = m2.get(k2);
+ if (h == null) {
+ h = headerInfoStrict("string");
+ m2.put(k2, h);
+ r2.header(name, h);
+ }
+ if (field.equals("collectionFormat"))
+ h.collectionFormat(value);
+ else if (field.equals("default"))
+ h._default(value);
+ else if (field.equals("description"))
+ h.description(value);
+ else if (field.equals("format"))
+ h.format(value);
+ else if (field.equals("items"))
+ h.items(jp.parse(value,
Items.class));
+ else if (field.equals("type"))
+ h.type(value);
+
+ } else if ("description".equals(name)) {
+ r2.description(value);
+ } else if ("schema".equals(name)) {
+ r2.schema(jp.parse(value,
SchemaInfo.class));
+ } else if ("examples".equals(name)) {
+ r2.examples(jp.parse(value,
TreeMap.class));
+ } else {
+ System.err.println("Unknown bundle key
'"+key+"'");
+ }
+ }
+ }
+
+ return m.isEmpty() ? null : m;
+ }
+
+ /**
+ * Returns <jk>true</jk> if the specified request object can call this
method.
+ */
+ boolean isRequestAllowed(RestRequest req) {
+ for (RestGuard guard : guards) {
+ req.setJavaMethod(method);
+ if (! guard.isRequestAllowed(req))
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Workhorse method.
+ *
+ * @param pathInfo The value of {@link
HttpServletRequest#getPathInfo()} (sorta)
+ * @return The HTTP response code.
+ */
+ int invoke(String pathInfo, RestRequest req, RestResponse res) throws
RestException {
+
+ String[] patternVals = pathPattern.match(pathInfo);
+ if (patternVals == null)
+ return SC_NOT_FOUND;
+
+ String remainder = null;
+ if (patternVals.length > pathPattern.getVars().length)
+ remainder = patternVals[pathPattern.getVars().length];
+ for (int i = 0; i < pathPattern.getVars().length; i++)
+ req.getPathMatch().put(pathPattern.getVars()[i],
patternVals[i]);
+ req.getPathMatch().setRemainder(remainder);
+
+ ObjectMap requestProperties =
createRequestProperties(properties, req);
+ req.init(method, requestProperties, defaultRequestHeaders,
defaultQuery, defaultFormData, defaultCharset,
+ serializers, parsers, urlEncodingParser, beanContext,
encoders, widgets);
+ res.init(requestProperties, defaultCharset, serializers,
urlEncodingSerializer, encoders);
+
+ // Class-level guards
+ for (RestGuard guard : context.getGuards())
+ if (! guard.guard(req, res))
+ return SC_UNAUTHORIZED;
+
+ // If the method implements matchers, test them.
+ for (RestMatcher m : requiredMatchers)
+ if (! m.matches(req))
+ return SC_PRECONDITION_FAILED;
+ if (optionalMatchers.length > 0) {
+ boolean matches = false;
+ for (RestMatcher m : optionalMatchers)
+ matches |= m.matches(req);
+ if (! matches)
+ return SC_PRECONDITION_FAILED;
+ }
+
+ context.preCall(req, res);
+
+ Object[] args = new Object[params.length];
+ for (int i = 0; i < params.length; i++) {
+ try {
+ args[i] = params[i].resolve(req, res);
+ } catch (RestException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new RestException(SC_BAD_REQUEST,
+ "Invalid data conversion. Could not
convert {0} ''{1}'' to type ''{2}'' on method ''{3}.{4}''.",
+ params[i].getParamType().name(),
params[i].getName(), params[i].getType(), method.getDeclaringClass().getName(),
method.getName()
+ ).initCause(e);
+ }
+ }
+
+ try {
+
+ for (RestGuard guard : guards)
+ if (! guard.guard(req, res))
+ return SC_OK;
+
+ Object output = method.invoke(context.getResource(),
args);
+ if (! method.getReturnType().equals(Void.TYPE))
+ if (output != null || !
res.getOutputStreamCalled())
+ res.setOutput(output);
+
+ context.postCall(req, res);
+
+ if (res.hasOutput()) {
+ output = res.getOutput();
+ for (RestConverter converter : converters)
+ output = converter.convert(req, output,
beanContext.getClassMetaForObject(output));
+ res.setOutput(output);
+ }
+ } catch (IllegalArgumentException e) {
+ throw new RestException(SC_BAD_REQUEST,
+ "Invalid argument type passed to the following
method: ''{0}''.\n\tArgument types: {1}",
+ method.toString(), getReadableClassNames(args)
+ ).initCause(e);
+ } catch (InvocationTargetException e) {
+ Throwable e2 = e.getTargetException(); // Get
the throwable thrown from the doX() method.
+ if (e2 instanceof RestException)
+ throw (RestException)e2;
+ if (e2 instanceof ParseException)
+ throw new RestException(SC_BAD_REQUEST, e2);
+ if (e2 instanceof InvalidDataConversionException)
+ throw new RestException(SC_BAD_REQUEST, e2);
+ throw new RestException(SC_INTERNAL_SERVER_ERROR, e2);
+ } catch (RestException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new RestException(SC_INTERNAL_SERVER_ERROR, e);
+ }
+ return SC_OK;
+ }
+
+ /**
+ * This method creates all the request-time properties.
+ */
+ ObjectMap createRequestProperties(final ObjectMap methodProperties,
final RestRequest req) {
+ @SuppressWarnings("serial")
+ ObjectMap m = new ObjectMap() {
+ @Override /* Map */
+ public Object get(Object key) {
+ Object o = super.get(key);
+ if (o == null) {
+ String k = key.toString();
+ int i = k.indexOf('.');
+ if (i != -1) {
+ String prefix = k.substring(0,
i);
+ String remainder =
k.substring(i+1);
+ Object v =
req.resolveProperty(CallMethod.this, prefix, remainder);
+ if (v != null)
+ return v;
+ }
+ o = req.getPathMatch().get(k);
+ if (o == null)
+ o = req.getHeader(k);
+ }
+ if (o instanceof String)
+ o =
req.getVarResolverSession().resolve(o.toString());
+ return o;
+ }
+ };
+ m.setInner(methodProperties);
+ return m;
+ }
+
+ @Override /* Object */
+ public String toString() {
+ return "SimpleMethod: name=" + httpMethod + ", path=" +
pathPattern.getPatternString();
+ }
+
+ /*
+ * compareTo() method is used to keep SimpleMethods ordered in the
CallRouter list.
+ * It maintains the order in which matches are made during requests.
+ */
+ @Override /* Comparable */
+ public int compareTo(CallMethod o) {
+ int c;
+
+ c = priority.compareTo(o.priority);
+ if (c != 0)
+ return c;
+
+ c = pathPattern.compareTo(o.pathPattern);
+ if (c != 0)
+ return c;
+
+ c = compare(o.requiredMatchers.length, requiredMatchers.length);
+ if (c != 0)
+ return c;
+
+ c = compare(o.optionalMatchers.length, optionalMatchers.length);
+ if (c != 0)
+ return c;
+
+ c = compare(o.guards.length, guards.length);
+ if (c != 0)
+ return c;
+
+ return 0;
+ }
+
+ @Override /* Object */
+ public boolean equals(Object o) {
+ if (! (o instanceof CallMethod))
+ return false;
+ return (compareTo((CallMethod)o) == 0);
+ }
+
+ @Override /* Object */
+ public int hashCode() {
+ return super.hashCode();
+ }
+}
\ No newline at end of file
Propchange:
release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/CallMethod.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added:
release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/CallRouter.java
==============================================================================
---
release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/CallRouter.java
(added)
+++
release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/CallRouter.java
Fri Sep 8 23:25:34 2017
@@ -0,0 +1,100 @@
+//
***************************************************************************************************************************
+// * 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.juneau.rest;
+
+import static javax.servlet.http.HttpServletResponse.*;
+
+import java.util.*;
+
+import javax.servlet.http.*;
+
+/**
+ * Represents a group of CallMethods on a REST resource that handle the same
HTTP Method name but with different
+ * paths/matchers/guards/etc...
+ *
+ * <p>
+ * Incoming requests for a particular HTTP method type (e.g. <js>"GET"</js>)
are handed off to this class and then
+ * dispatched to the appropriate CallMethod.
+ */
+class CallRouter {
+ private final CallMethod[] callMethods;
+
+ private CallRouter(CallMethod[] callMethods) {
+ this.callMethods = callMethods;
+ }
+
+ /**
+ * Builder class.
+ */
+ static class Builder {
+ private List<CallMethod> childMethods = new
ArrayList<CallMethod>();
+ private Set<String> collisions = new HashSet<String>();
+ private String httpMethodName;
+
+ Builder(String httpMethodName) {
+ this.httpMethodName = httpMethodName;
+ }
+
+ String getHttpMethodName() {
+ return httpMethodName;
+ }
+
+ Builder add(CallMethod m) throws RestServletException {
+ if (! m.hasGuardsOrMatchers()) {
+ String p = m.getHttpMethod() + ":" +
m.getPathPattern();
+ if (collisions.contains(p))
+ throw new
RestServletException("Duplicate Java methods assigned to the same
method/pattern: ''{0}''", p);
+ collisions.add(p);
+ }
+ childMethods.add(m);
+ return this;
+ }
+
+ CallRouter build() {
+ Collections.sort(childMethods);
+ return new CallRouter(childMethods.toArray(new
CallMethod[childMethods.size()]));
+ }
+ }
+
+ /**
+ * Workhorse method.
+ *
+ * <p>
+ * Routes this request to one of the CallMethods.
+ *
+ * @param pathInfo The value of {@link
HttpServletRequest#getPathInfo()} (sorta)
+ * @return The HTTP response code.
+ */
+ int invoke(String pathInfo, RestRequest req, RestResponse res) throws
RestException {
+ if (callMethods.length == 1)
+ return callMethods[0].invoke(pathInfo, req, res);
+
+ int maxRc = 0;
+ for (CallMethod m : callMethods) {
+ int rc = m.invoke(pathInfo, req, res);
+ if (rc == SC_OK)
+ return SC_OK;
+ maxRc = Math.max(maxRc, rc);
+ }
+ return maxRc;
+ }
+
+ @Override /* Object */
+ public String toString() {
+ StringBuilder sb = new StringBuilder("CallRouter: [\n");
+ for (CallMethod sm : callMethods)
+ sb.append("\t" + sm + "\n");
+ sb.append("]");
+ return sb.toString();
+ }
+}
\ No newline at end of file
Propchange:
release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/CallRouter.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added:
release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/ClientVersionMatcher.java
==============================================================================
---
release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/ClientVersionMatcher.java
(added)
+++
release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/ClientVersionMatcher.java
Fri Sep 8 23:25:34 2017
@@ -0,0 +1,54 @@
+//
***************************************************************************************************************************
+// * 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.juneau.rest;
+
+import static org.apache.juneau.internal.StringUtils.*;
+
+import org.apache.juneau.internal.*;
+import org.apache.juneau.rest.annotation.*;
+
+/**
+ * Specialized matcher for matching client versions.
+ *
+ * <p>
+ * See {@link RestResource#clientVersionHeader} and {@link
RestMethod#clientVersion} for more info.
+ */
+public class ClientVersionMatcher extends RestMatcher {
+
+ private final String clientVersionHeader;
+ private final VersionRange range;
+
+ /**
+ * Constructor.
+ *
+ * @param clientVersionHeader
+ * The HTTP request header name containing the client version.
+ * If <jk>null</jk> or an empty string, uses
<js>"X-Client-Version"</js>
+ * @param javaMethod The version string that the client version must
match.
+ */
+ protected ClientVersionMatcher(String clientVersionHeader,
java.lang.reflect.Method javaMethod) {
+ this.clientVersionHeader = isEmpty(clientVersionHeader) ?
"X-Client-Version" : clientVersionHeader;
+ RestMethod m = javaMethod.getAnnotation(RestMethod.class);
+ range = new VersionRange(m.clientVersion());
+ }
+
+ @Override /* RestMatcher */
+ public boolean matches(RestRequest req) {
+ return range.matches(req.getHeader(clientVersionHeader));
+ }
+
+ @Override /* RestMatcher */
+ public boolean mustMatch() {
+ return true;
+ }
+}
Propchange:
release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/ClientVersionMatcher.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added:
release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/ReaderResource.java
==============================================================================
---
release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/ReaderResource.java
(added)
+++
release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/ReaderResource.java
Fri Sep 8 23:25:34 2017
@@ -0,0 +1,265 @@
+//
***************************************************************************************************************************
+// * 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.juneau.rest;
+
+import static org.apache.juneau.internal.IOUtils.*;
+
+import java.io.*;
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.http.*;
+import org.apache.juneau.internal.*;
+import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.response.*;
+import org.apache.juneau.svl.*;
+
+/**
+ * Represents the contents of a text file with convenience methods for
resolving {@link Parameter} variables and adding
+ * HTTP response headers.
+ *
+ * <p>
+ * This class is handled special by the {@link WritableHandler} class.
+ */
+public class ReaderResource implements Writable {
+
+ private final MediaType mediaType;
+ private final String[] contents;
+ private final VarResolverSession varSession;
+ private final Map<String,String> headers;
+
+ /**
+ * Constructor.
+ *
+ * @param mediaType The HTTP media type.
+ * @param contents
+ * The contents of this resource.
+ * <br>If multiple contents are specified, the results will be
concatenated.
+ * <br>Contents can be any of the following:
+ * <ul>
+ * <li><code>CharSequence</code>
+ * <li><code>Reader</code>
+ * <li><code>File</code>
+ * </ul>
+ * @throws IOException
+ */
+ protected ReaderResource(MediaType mediaType, Object...contents) throws
IOException {
+ this(mediaType, null, null, contents);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param mediaType The resource media type.
+ * @param headers The HTTP response headers for this streamed resource.
+ * @param varSession Optional variable resolver for resolving variables
in the string.
+ * @param contents
+ * The resource contents.
+ * <br>If multiple contents are specified, the results will be
concatenated.
+ * <br>Contents can be any of the following:
+ * <ul>
+ * <li><code>InputStream</code>
+ * <li><code>Reader</code> - Converted to UTF-8 bytes.
+ * <li><code>File</code>
+ * <li><code>CharSequence</code> - Converted to UTF-8
bytes.
+ * </ul>
+ * @throws IOException
+ */
+ public ReaderResource(MediaType mediaType, Map<String,String> headers,
VarResolverSession varSession, Object...contents) throws IOException {
+ this.mediaType = mediaType;
+ this.varSession = varSession;
+
+ Map<String,String> m = new LinkedHashMap<String,String>();
+ if (headers != null)
+ for (Map.Entry<String,String> e : headers.entrySet())
+ m.put(e.getKey(),
StringUtils.toString(e.getValue()));
+ this.headers = Collections.unmodifiableMap(m);
+
+ this.contents = new String[contents.length];
+ for (int i = 0; i < contents.length; i++) {
+ Object c = contents[i];
+ if (c == null)
+ this.contents[i] = "";
+ else if (c instanceof InputStream)
+ this.contents[i] = read((InputStream)c);
+ else if (c instanceof File)
+ this.contents[i] = read((File)c);
+ else if (c instanceof Reader)
+ this.contents[i] = read((Reader)c);
+ else if (c instanceof CharSequence)
+ this.contents[i] = ((CharSequence)c).toString();
+ else
+ throw new IOException("Invalid class type
passed to ReaderResource: " + c.getClass().getName());
+ }
+ }
+
+ /**
+ * Builder class for constructing {@link ReaderResource} objects.
+ */
+ @SuppressWarnings("hiding")
+ public static class Builder {
+ ArrayList<Object> contents = new ArrayList<Object>();
+ MediaType mediaType;
+ VarResolverSession varResolver;
+ Map<String,String> headers = new LinkedHashMap<String,String>();
+
+ /**
+ * Specifies the resource media type string.
+ *
+ * @param mediaType The resource media type string.
+ * @return This object (for method chaining).
+ */
+ public Builder mediaType(String mediaType) {
+ this.mediaType = MediaType.forString(mediaType);
+ return this;
+ }
+
+ /**
+ * Specifies the resource media type string.
+ *
+ * @param mediaType The resource media type string.
+ * @return This object (for method chaining).
+ */
+ public Builder mediaType(MediaType mediaType) {
+ this.mediaType = mediaType;
+ return this;
+ }
+
+ /**
+ * Specifies the contents for this resource.
+ *
+ * <p>
+ * This method can be called multiple times to add more content.
+ *
+ * @param contents
+ * The resource contents.
+ * <br>If multiple contents are specified, the results
will be concatenated.
+ * <br>Contents can be any of the following:
+ * <ul>
+ * <li><code>InputStream</code>
+ * <li><code>Reader</code> - Converted to UTF-8
bytes.
+ * <li><code>File</code>
+ * <li><code>CharSequence</code> - Converted to
UTF-8 bytes.
+ * </ul>
+ * @return This object (for method chaining).
+ */
+ public Builder contents(Object...contents) {
+ this.contents.addAll(Arrays.asList(contents));
+ return this;
+ }
+
+ /**
+ * Specifies an HTTP response header value.
+ *
+ * @param name The HTTP header name.
+ * @param value
+ * The HTTP header value.
+ * Will be converted to a <code>String</code> using {@link
Object#toString()}.
+ * @return This object (for method chaining).
+ */
+ public Builder header(String name, Object value) {
+ this.headers.put(name, StringUtils.toString(value));
+ return this;
+ }
+
+ /**
+ * Specifies HTTP response header values.
+ *
+ * @param headers
+ * The HTTP headers.
+ * Values will be converted to <code>Strings</code> using
{@link Object#toString()}.
+ * @return This object (for method chaining).
+ */
+ public Builder headers(Map<String,Object> headers) {
+ for (Map.Entry<String,Object> e : headers.entrySet())
+ header(e.getKey(), e.getValue());
+ return this;
+ }
+
+ /**
+ * Specifies the variable resolver to use for this resource.
+ *
+ * @param varResolver The variable resolver.
+ * @return This object (for method chaining).
+ */
+ public Builder varResolver(VarResolverSession varResolver) {
+ this.varResolver = varResolver;
+ return this;
+ }
+
+ /**
+ * Create a new {@link ReaderResource} using values in this
builder.
+ *
+ * @return A new immutable {@link ReaderResource} object.
+ * @throws IOException
+ */
+ public ReaderResource build() throws IOException {
+ return new ReaderResource(mediaType, headers,
varResolver, contents.toArray());
+ }
+ }
+
+ /**
+ * Get the HTTP response headers.
+ *
+ * @return The HTTP response headers.
+ */
+ public Map<String,String> getHeaders() {
+ return headers;
+ }
+
+ @Override /* Writeable */
+ public void writeTo(Writer w) throws IOException {
+ for (String s : contents) {
+ if (varSession != null)
+ varSession.resolveTo(s, w);
+ else
+ w.write(s);
+ }
+ }
+
+ @Override /* Writeable */
+ public MediaType getMediaType() {
+ return mediaType;
+ }
+
+ @Override /* Object */
+ public String toString() {
+ if (contents.length == 1 && varSession == null)
+ return contents[0];
+ StringWriter sw = new StringWriter();
+ for (String s : contents) {
+ if (varSession != null)
+ return varSession.resolve(s);
+ sw.write(s);
+ }
+ return sw.toString();
+ }
+
+ /**
+ * Same as {@link #toString()} but strips comments from the text before
returning it.
+ *
+ * <p>
+ * Supports stripping comments from the following media types: HTML,
XHTML, XML, JSON, Javascript, CSS.
+ *
+ * @return The resource contents stripped of any comments.
+ */
+ public String toCommentStrippedString() {
+ String s = toString();
+ String subType = mediaType.getSubType();
+ if ("html".equals(subType) || "xhtml".equals(subType) ||
"xml".equals(subType))
+ s = s.replaceAll("(?s)<!--(.*?)-->\\s*", "");
+ else if ("json".equals(subType) || "javascript".equals(subType)
|| "css".equals(subType))
+ s = s.replaceAll("(?s)\\/\\*(.*?)\\*\\/\\s*", "");
+ return s;
+ }
+}
Propchange:
release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/ReaderResource.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added:
release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/Redirect.java
==============================================================================
---
release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/Redirect.java
(added)
+++
release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/Redirect.java
Fri Sep 8 23:25:34 2017
@@ -0,0 +1,165 @@
+//
***************************************************************************************************************************
+// * 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.juneau.rest;
+
+import static org.apache.juneau.internal.StringUtils.*;
+
+import java.net.*;
+import java.text.*;
+
+import org.apache.juneau.urlencoding.*;
+
+/**
+ * REST methods can return this object as a shortcut for performing <code>HTTP
302</code> redirects.
+ *
+ * <p>
+ * The following example shows the difference between handling redirects via
the {@link RestRequest}/{@link RestResponse},
+ * and the simplified approach of using this class.
+ * <p class='bcode'>
+ * <jc>// Redirect to "/contextPath/servletPath/foobar"</jc>
+ *
+ * <jc>// Using RestRequest and RestResponse</jc>
+ * <ja>@RestMethod</ja>(name=<js>"GET"</js>, path=<js>"/example1"</js>)
+ * <jk>public void</jk> example1(RestRequest req, RestResponse res)
<jk>throws</jk> IOException {
+ * res.sendRedirect(req.getServletURI() + <js>"/foobar"</js>);
+ * }
+ *
+ * <jc>// Using Redirect</jc>
+ * <ja>@RestMethod</ja>(name=<js>"GET"</js>, path=<js>"/example2"</js>)
+ * <jk>public</jk> Redirect example2() {
+ * <jk>return new</jk> Redirect(<js>"foobar"</js>);
+ * }
+ * </p>
+ *
+ * <p>
+ * The constructor can use a {@link MessageFormat}-style pattern with multiple
arguments:
+ * <p class='bcode'>
+ * <ja>@RestMethod</ja>(name=<js>"GET"</js>, path=<js>"/example3"</js>)
+ * <jk>public</jk> Redirect example3() {
+ * <jk>return new</jk> Redirect(<js>"foo/{0}/bar/{1}"</js>, id1,
id2);
+ * }
+ * </p>
+ *
+ * <p>
+ * The arguments are serialized to strings using the servlet's {@link
UrlEncodingSerializer}, so any filters defined on
+ * the serializer or REST method/class will be used when present.
+ * The arguments will also be automatically URL-encoded.
+ *
+ * <p>
+ * Redirecting to the servlet root can be accomplished by simply using the
no-arg constructor.
+ * <p class='bcode'>
+ * <jc>// Simply redirect to the servlet root.
+ * // Equivalent to res.sendRedirect(req.getServletURI()).</jc>
+ * <ja>@RestMethod</ja>(name=<js>"GET"</js>, path=<js>"/example4"</js>)
+ * <jk>public</jk> Redirect exmaple4() {
+ * <jk>return new</jk> Redirect();
+ * }
+ * </p>
+ *
+ * <p>
+ * This class is handled by {@link
org.apache.juneau.rest.response.RedirectHandler}, a built-in default response
+ * handler created in {@link RestConfig}.
+ */
+public final class Redirect {
+
+ private final int httpResponseCode;
+ private final URI uri;
+
+ /**
+ * Redirect to the specified URL.
+ *
+ * <p>
+ * Relative paths are interpreted as relative to the servlet path.
+ *
+ * @param uri
+ * The URL to redirect to.
+ * <br>Can be any of the following:
+ * <ul>
+ * <li><code>URL</code>
+ * <li><code>URI</code>
+ * <li><code>CharSequence</code>
+ * </ul>
+ * @param args Optional {@link MessageFormat}-style arguments.
+ */
+ public Redirect(Object uri, Object...args) {
+ this(0, uri, args);
+ }
+
+ /**
+ * Convenience method for redirecting to instance of {@link URL} and
{@link URI}.
+ *
+ * <p>
+ * Same as calling <code>toString()</code> on the object and using the
other constructor.
+ *
+ * @param uri
+ * The URL to redirect to.
+ * <br>Can be any of the following:
+ * <ul>
+ * <li><code>URL</code>
+ * <li><code>URI</code>
+ * <li><code>CharSequence</code>
+ * </ul>
+ */
+ public Redirect(Object uri) {
+ this(0, uri, (Object[])null);
+ }
+
+ /**
+ * Redirect to the specified URL.
+ *
+ * <p>
+ * Relative paths are interpreted as relative to the servlet path.
+ *
+ * @param httpResponseCode The HTTP response code.
+ * @param url
+ * The URL to redirect to.
+ * <br>Can be any of the following:
+ * <ul>
+ * <li><code>URL</code>
+ * <li><code>URI</code>
+ * <li><code>CharSequence</code>
+ * </ul>
+ * @param args Optional {@link MessageFormat}-style arguments.
+ */
+ public Redirect(int httpResponseCode, Object url, Object...args) {
+ this.httpResponseCode = httpResponseCode;
+ if (url == null)
+ url = "";
+ this.uri = toURI(format(url.toString(), args));
+ }
+
+ /**
+ * Shortcut for redirecting to the servlet root.
+ */
+ public Redirect() {
+ this(0, null, (Object[])null);
+ }
+
+ /**
+ * Returns the response code passed in through the constructor.
+ *
+ * @return The response code passed in through the constructor, or
<code>0</code> if response code wasn't specified.
+ */
+ public int getHttpResponseCode() {
+ return httpResponseCode;
+ }
+
+ /**
+ * Returns the URI to redirect to.
+ *
+ * @return The URI to redirect to.
+ */
+ public URI getURI() {
+ return uri;
+ }
+}
Propchange:
release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/Redirect.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added:
release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestBody.java
==============================================================================
---
release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestBody.java
(added)
+++
release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestBody.java
Fri Sep 8 23:25:34 2017
@@ -0,0 +1,457 @@
+//
***************************************************************************************************************************
+// * 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.juneau.rest;
+
+import static javax.servlet.http.HttpServletResponse.*;
+import static org.apache.juneau.internal.IOUtils.*;
+import static org.apache.juneau.internal.StringUtils.*;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+import javax.servlet.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.encoders.*;
+import org.apache.juneau.http.*;
+import org.apache.juneau.internal.*;
+import org.apache.juneau.parser.*;
+import org.apache.juneau.urlencoding.*;
+
+/**
+ * Contains the body of the HTTP request.
+ */
+@SuppressWarnings("unchecked")
+public class RequestBody {
+
+ private byte[] body;
+ private final RestRequest req;
+ private EncoderGroup encoders;
+ private Encoder encoder;
+ private ParserGroup parsers;
+ private UrlEncodingParser urlEncodingParser;
+ private RequestHeaders headers;
+ private BeanSession beanSession;
+ private int contentLength = 0;
+
+ RequestBody(RestRequest req) {
+ this.req = req;
+ }
+
+ RequestBody setEncoders(EncoderGroup encoders) {
+ this.encoders = encoders;
+ return this;
+ }
+
+ RequestBody setParsers(ParserGroup parsers) {
+ this.parsers = parsers;
+ return this;
+ }
+
+ RequestBody setHeaders(RequestHeaders headers) {
+ this.headers = headers;
+ return this;
+ }
+
+ RequestBody setUrlEncodingParser(UrlEncodingParser urlEncodingParser) {
+ this.urlEncodingParser = urlEncodingParser;
+ return this;
+ }
+
+ RequestBody setBeanSession(BeanSession beanSession) {
+ this.beanSession = beanSession;
+ return this;
+ }
+
+ @SuppressWarnings("hiding")
+ RequestBody load(byte[] body) {
+ this.body = body;
+ return this;
+ }
+
+ boolean isLoaded() {
+ return body != null;
+ }
+
+ /**
+ * Reads the input from the HTTP request as JSON, XML, or HTML and
converts the input to a POJO.
+ *
+ * <p>
+ * If {@code allowHeaderParams} init parameter is <jk>true</jk>, then
first looks for {@code &body=xxx} in the URL
+ * query string.
+ *
+ * <p>
+ * If type is <jk>null</jk> or <code>Object.<jk>class</jk></code>, then
the actual type will be determined
+ * automatically based on the following input:
+ * <table class='styled'>
+ * <tr><th>Type</th><th>JSON input</th><th>XML
input</th><th>Return type</th></tr>
+ * <tr>
+ * <td>object</td>
+ * <td><js>"{...}"</js></td>
+ *
<td><code><xt><object></xt>...<xt></object></xt></code><br><code><xt><x</xt>
<xa>type</xa>=<xs>'object'</xs><xt>></xt>...<xt></x></xt></code></td>
+ * <td>{@link ObjectMap}</td>
+ * </tr>
+ * <tr>
+ * <td>array</td>
+ * <td><js>"[...]"</js></td>
+ *
<td><code><xt><array></xt>...<xt></array></xt></code><br><code><xt><x</xt>
<xa>type</xa>=<xs>'array'</xs><xt>></xt>...<xt></x></xt></code></td>
+ * <td>{@link ObjectList}</td>
+ * </tr>
+ * <tr>
+ * <td>string</td>
+ * <td><js>"'...'"</js></td>
+ *
<td><code><xt><string></xt>...<xt></string></xt></code><br><code><xt><x</xt>
<xa>type</xa>=<xs>'string'</xs><xt>></xt>...<xt></x></xt></code></td>
+ * <td>{@link String}</td>
+ * </tr>
+ * <tr>
+ * <td>number</td>
+ * <td><code>123</code></td>
+ *
<td><code><xt><number></xt>123<xt></number></xt></code><br><code><xt><x</xt>
<xa>type</xa>=<xs>'number'</xs><xt>></xt>...<xt></x></xt></code></td>
+ * <td>{@link Number}</td>
+ * </tr>
+ * <tr>
+ * <td>boolean</td>
+ * <td><jk>true</jk></td>
+ *
<td><code><xt><boolean></xt>true<xt></boolean></xt></code><br><code><xt><x</xt>
<xa>type</xa>=<xs>'boolean'</xs><xt>></xt>...<xt></x></xt></code></td>
+ * <td>{@link Boolean}</td>
+ * </tr>
+ * <tr>
+ * <td>null</td>
+ * <td><jk>null</jk> or blank</td>
+ * <td><code><xt><null/></xt></code> or
blank<br><code><xt><x</xt>
<xa>type</xa>=<xs>'null'</xs><xt>/></xt></code></td>
+ * <td><jk>null</jk></td>
+ * </tr>
+ * </table>
+ *
+ * <p>
+ * Refer to <a class="doclink"
href="../../../../overview-summary.html#Core.PojoCategories">POJO
Categories</a> for
+ * a complete definition of supported POJOs.
+ *
+ * <h5 class='section'>Examples:</h5>
+ * <p class='bcode'>
+ * <jc>// Parse into an integer.</jc>
+ * <jk>int</jk> body =
req.getBody().asType(<jk>int</jk>.<jk>class</jk>);
+ *
+ * <jc>// Parse into an int array.</jc>
+ * <jk>int</jk>[] body =
req.getBody().asType(<jk>int</jk>[].<jk>class</jk>);
+
+ * <jc>// Parse into a bean.</jc>
+ * MyBean body = req.getBody().asType(MyBean.<jk>class</jk>);
+ *
+ * <jc>// Parse into a linked-list of objects.</jc>
+ * List body = req.getBody().asType(LinkedList.<jk>class</jk>);
+ *
+ * <jc>// Parse into a map of object keys/values.</jc>
+ * Map body = req.getBody().asType(TreeMap.<jk>class</jk>);
+ * </p>
+ *
+ * @param type The class type to instantiate.
+ * @param <T> The class type to instantiate.
+ * @return The input parsed to a POJO.
+ * @throws IOException If a problem occurred trying to read from the
reader.
+ * @throws ParseException
+ * If the input contains a syntax error or is malformed for the
requested {@code Accept} header or is not valid
+ * for the specified type.
+ */
+ public <T> T asType(Class<T> type) throws IOException, ParseException {
+ return parse(beanSession.getClassMeta(type));
+ }
+
+ /**
+ * Reads the input from the HTTP request as JSON, XML, or HTML and
converts the input to a POJO.
+ *
+ * <h5 class='section'>Examples:</h5>
+ * <p class='bcode'>
+ * <jc>// Parse into a linked-list of strings.</jc>
+ * List<String> body =
req.getBody().asType(LinkedList.<jk>class</jk>, String.<jk>class</jk>);
+ *
+ * <jc>// Parse into a linked-list of linked-lists of strings.</jc>
+ * List<List<String>> body =
req.getBody().asType(LinkedList.<jk>class</jk>, LinkedList.<jk>class</jk>,
String.<jk>class</jk>);
+ *
+ * <jc>// Parse into a map of string keys/values.</jc>
+ * Map<String,String> body =
req.getBody().asType(TreeMap.<jk>class</jk>, String.<jk>class</jk>,
String.<jk>class</jk>);
+ *
+ * <jc>// Parse into a map containing string keys and values of
lists containing beans.</jc>
+ * Map<String,List<MyBean>> body =
req.getBody().asType(TreeMap.<jk>class</jk>, String.<jk>class</jk>,
List.<jk>class</jk>, MyBean.<jk>class</jk>);
+ * </p>
+ *
+ * @param type
+ * The type of object to create.
+ * <br>Can be any of the following: {@link ClassMeta}, {@link
Class}, {@link ParameterizedType},
+ * {@link GenericArrayType}
+ * @param args
+ * The type arguments of the class if it's a collection or map.
+ * <br>Can be any of the following: {@link ClassMeta}, {@link
Class}, {@link ParameterizedType},
+ * {@link GenericArrayType}
+ * <br>Ignored if the main type is not a map or collection.
+ * @param <T> The class type to instantiate.
+ * @return The input parsed to a POJO.
+ */
+ public <T> T asType(Type type, Type...args) {
+ return (T)parse(beanSession.getClassMeta(type, args));
+ }
+
+ /**
+ * Returns the HTTP body content as a plain string.
+ *
+ * <p>
+ * If {@code allowHeaderParams} init parameter is true, then first
looks for {@code &body=xxx} in the URL query
+ * string.
+ *
+ * @return The incoming input from the connection as a plain string.
+ * @throws IOException If a problem occurred trying to read from the
reader.
+ */
+ public String asString() throws IOException {
+ if (body == null)
+ body = readBytes(getInputStream(), 1024);
+ return new String(body, UTF8);
+ }
+
+ /**
+ * Returns the HTTP body content as a simple hexadecimal character
string.
+ *
+ * @return The incoming input from the connection as a plain string.
+ * @throws IOException If a problem occurred trying to read from the
reader.
+ */
+ public String asHex() throws IOException {
+ if (body == null)
+ body = readBytes(getInputStream(), 1024);
+ return toHex(body);
+ }
+
+ /**
+ * Returns the HTTP body content as a {@link Reader}.
+ *
+ * <p>
+ * If {@code allowHeaderParams} init parameter is true, then first
looks for {@code &body=xxx} in the URL query
+ * string.
+ *
+ * <p>
+ * Automatically handles GZipped input streams.
+ *
+ * @return The body contents as a reader.
+ * @throws IOException
+ */
+ public BufferedReader getReader() throws IOException {
+ Reader r = getUnbufferedReader();
+ if (r instanceof BufferedReader)
+ return (BufferedReader)r;
+ int len = req.getContentLength();
+ int buffSize = len <= 0 ? 8192 : Math.max(len, 8192);
+ return new BufferedReader(r, buffSize);
+ }
+
+ /**
+ * Same as {@link #getReader()}, but doesn't encapsulate the result in
a {@link BufferedReader};
+ *
+ * @return An unbuffered reader.
+ * @throws IOException
+ */
+ protected Reader getUnbufferedReader() throws IOException {
+ if (body != null)
+ return new CharSequenceReader(new String(body, UTF8));
+ return new InputStreamReader(getInputStream(),
req.getCharacterEncoding());
+ }
+
+ /**
+ * Returns the HTTP body content as an {@link InputStream}.
+ *
+ * <p>
+ * Automatically handles GZipped input streams.
+ *
+ * @return The negotiated input stream.
+ * @throws IOException If any error occurred while trying to get the
input stream or wrap it in the GZIP wrapper.
+ */
+ public ServletInputStream getInputStream() throws IOException {
+
+ if (body != null)
+ return new ServletInputStream2(body);
+
+ Encoder enc = getEncoder();
+
+ ServletInputStream is = req.getRawInputStream();
+ if (enc != null) {
+ final InputStream is2 = enc.getInputStream(is);
+ return new ServletInputStream2(is2);
+ }
+ return is;
+ }
+
+ /**
+ * Returns the parser and media type matching the request
<code>Content-Type</code> header.
+ *
+ * @return
+ * The parser matching the request <code>Content-Type</code>
header, or <jk>null</jk> if no matching parser was
+ * found.
+ * Includes the matching media type.
+ */
+ public ParserMatch getParserMatch() {
+ MediaType mediaType = headers.getContentType();
+ if (isEmpty(mediaType)) {
+ if (body != null)
+ mediaType = MediaType.UON;
+ else
+ mediaType = MediaType.JSON;
+ }
+ ParserMatch pm = parsers.getParserMatch(mediaType);
+
+ // If no patching parser for URL-encoding, use the one defined
on the servlet.
+ if (pm == null && mediaType.equals(MediaType.URLENCODING))
+ pm = new ParserMatch(MediaType.URLENCODING,
urlEncodingParser);
+
+ return pm;
+ }
+
+ /**
+ * Returns the parser matching the request <code>Content-Type</code>
header.
+ *
+ * @return
+ * The parser matching the request <code>Content-Type</code>
header, or <jk>null</jk> if no matching parser was
+ * found.
+ */
+ public Parser getParser() {
+ ParserMatch pm = getParserMatch();
+ return (pm == null ? null : pm.getParser());
+ }
+
+ /**
+ * Returns the reader parser matching the request
<code>Content-Type</code> header.
+ *
+ * @return
+ * The reader parser matching the request
<code>Content-Type</code> header, or <jk>null</jk> if no matching
+ * reader parser was found, or the matching parser was an input
stream parser.
+ */
+ public ReaderParser getReaderParser() {
+ Parser p = getParser();
+ if (p != null && p.isReaderParser())
+ return (ReaderParser)p;
+ return null;
+ }
+
+ /* Workhorse method */
+ private <T> T parse(ClassMeta<T> cm) throws RestException {
+
+ try {
+ if (cm.isReader())
+ return (T)getReader();
+
+ if (cm.isInputStream())
+ return (T)getInputStream();
+
+ TimeZone timeZone = headers.getTimeZone();
+ Locale locale = req.getLocale();
+ ParserMatch pm = getParserMatch();
+
+ if (pm != null) {
+ Parser p = pm.getParser();
+ MediaType mediaType = pm.getMediaType();
+ try {
+ req.getProperties().append("mediaType",
mediaType).append("characterEncoding", req.getCharacterEncoding());
+ ParserSession session =
p.createSession(new ParserSessionArgs(req.getProperties(), req.getJavaMethod(),
locale, timeZone, mediaType, req.getContext().getResource()));
+ Object in = session.isReaderParser() ?
getUnbufferedReader() : getInputStream();
+ return session.parse(in, cm);
+ } catch (ParseException e) {
+ throw new RestException(SC_BAD_REQUEST,
+ "Could not convert request body
content to class type ''{0}'' using parser ''{1}''.",
+ cm, p.getClass().getName()
+ ).initCause(e);
+ }
+ }
+
+ throw new RestException(SC_UNSUPPORTED_MEDIA_TYPE,
+ "Unsupported media-type in request header
''Content-Type'': ''{0}''\n\tSupported media-types: {1}",
+ headers.getContentType(),
req.getParserGroup().getSupportedMediaTypes()
+ );
+
+ } catch (IOException e) {
+ throw new RestException(SC_INTERNAL_SERVER_ERROR,
+ "I/O exception occurred while attempting to
handle request ''{0}''.",
+ req.getDescription()
+ ).initCause(e);
+ }
+ }
+
+ private Encoder getEncoder() {
+ if (encoder == null) {
+ String ce = req.getHeader("content-encoding");
+ if (! isEmpty(ce)) {
+ ce = ce.trim();
+ encoder = encoders.getEncoder(ce);
+ if (encoder == null)
+ throw new
RestException(SC_UNSUPPORTED_MEDIA_TYPE,
+ "Unsupported encoding in
request header ''Content-Encoding'': ''{0}''\n\tSupported codings: {1}",
+
req.getHeader("content-encoding"), encoders.getSupportedEncodings()
+ );
+ }
+
+ if (encoder != null)
+ contentLength = -1;
+ }
+ // Note that if this is the identity encoder, we want to return
null
+ // so that we don't needlessly wrap the input stream.
+ if (encoder == IdentityEncoder.INSTANCE)
+ return null;
+ return encoder;
+ }
+
+ /**
+ * Returns the content length of the body.
+ *
+ * @return The content length of the body in bytes.
+ */
+ public int getContentLength() {
+ return contentLength == 0 ? req.getRawContentLength() :
contentLength;
+ }
+
+ /**
+ * ServletInputStream wrapper around a normal input stream.
+ */
+ private static class ServletInputStream2 extends ServletInputStream {
+
+ private final InputStream is;
+
+ private ServletInputStream2(InputStream is) {
+ this.is = is;
+ }
+
+ private ServletInputStream2(byte[] b) {
+ this(new ByteArrayInputStream(b));
+ }
+
+ @Override /* InputStream */
+ public final int read() throws IOException {
+ return is.read();
+ }
+
+ @Override /* InputStream */
+ public final void close() throws IOException {
+ is.close();
+ }
+
+ @Override /* ServletInputStream */
+ public boolean isFinished() {
+ return false;
+ }
+
+ @Override /* ServletInputStream */
+ public boolean isReady() {
+ return true;
+ }
+
+ @Override /* ServletInputStream */
+ public void setReadListener(ReadListener arg0) {
+ throw new NoSuchMethodError();
+ }
+ }
+}
Propchange:
release/incubator/juneau/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestBody.java
------------------------------------------------------------------------------
svn:mime-type = text/plain