http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/1b4f98a0/org.apache.juneau.server/src/main/java/org/apache/juneau/server/RestConverter.java ---------------------------------------------------------------------- diff --git a/org.apache.juneau.server/src/main/java/org/apache/juneau/server/RestConverter.java b/org.apache.juneau.server/src/main/java/org/apache/juneau/server/RestConverter.java new file mode 100755 index 0000000..0c1c9ec --- /dev/null +++ b/org.apache.juneau.server/src/main/java/org/apache/juneau/server/RestConverter.java @@ -0,0 +1,74 @@ +/*************************************************************************************************************************** + * 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.server; + +import org.apache.juneau.*; +import org.apache.juneau.serializer.*; +import org.apache.juneau.server.annotation.*; +import org.apache.juneau.server.converters.*; + +/** + * REST method response converter. + * <p> + * Implements a filter mechanism for REST method calls that allows response objects to be + * converted to some other POJO after invocation of the REST method. + * <p> + * Converters are associated with REST methods through the {@link RestMethod#converters()} annotation. + * <h6 class='topic'>Example</h6> + * <p class='bcode'> + * <jk>public class</jk> RequestEchoResource <jk>extends</jk> RestServlet { + * + * <jc>// GET request handler</jc> + * <ja>@RestMethod</ja>(name=<js>"GET"</js>, path=<js>"/*"</js>, converters={Queryable.<jk>class</jk>,Traversable.<jk>class</jk>}) + * <jk>public</jk> HttpServletRequest doGet(RestRequest req) { + * res.setTitle(<js>"Contents of HttpServletRequest object"</js>); + * <jk>return</jk> req; + * } + * } + * </p> + * <p> + * Converters can also be associated at the servlet level using the {@link RestResource#converters()} annotation. + * Applying converters at the resource level is equivalent to applying converters to each resource method individually. + * + * <h6 class='topic'>How to implement</h6> + * <p> + * Implementers should simply implement the {@link #convert(RestRequest, Object, ClassMeta)} and + * return back a 'converted' object. + * It's up to the implementer to decide what this means. + * <p> + * Converters must implement a no-args constructor. + * + * <h6 class='topic'>Predefined converters</h6> + * <p> + * The following converters are available by default. + * <ul class='spaced-list'> + * <li>{@link Traversable} - Allows URL additional path info to address individual elements in a POJO tree. + * <li>{@link Queryable} - Allows query/view/sort functions to be performed on POJOs. + * <li>{@link Introspectable} - Allows Java public methods to be invoked on the returned POJOs. + * </ul> + */ +public interface RestConverter { + + /** + * Performs post-call conversion on the specified response object. + * + * @param req The servlet request. + * @param res The response object set by the REST method through the {@link RestResponse#setOutput(Object)} method. + * @param cm The {@link ClassMeta} on the object from the bean context of the servlet. + * Can be used to check if the object has any filters. + * @return The converted object. + * @throws RestException Thrown if any errors occur during conversion. + * @throws SerializeException + */ + public Object convert(RestRequest req, Object res, ClassMeta<?> cm) throws RestException, SerializeException; +}
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/1b4f98a0/org.apache.juneau.server/src/main/java/org/apache/juneau/server/RestException.java ---------------------------------------------------------------------- diff --git a/org.apache.juneau.server/src/main/java/org/apache/juneau/server/RestException.java b/org.apache.juneau.server/src/main/java/org/apache/juneau/server/RestException.java new file mode 100755 index 0000000..62ea6ae --- /dev/null +++ b/org.apache.juneau.server/src/main/java/org/apache/juneau/server/RestException.java @@ -0,0 +1,137 @@ +/*************************************************************************************************************************** + * 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.server; + +import java.text.*; + +/** + * Exception thrown to trigger an error HTTP status. + * <p> + * REST methods on subclasses of {@link RestServlet} can throw + * this exception to trigger an HTTP status other than the automatically-generated + * <code>404</code>, <code>405</code>, and <code>500</code> statuses. + * + * @author James Bognar ([email protected]) + */ +public class RestException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + private int status; + private int occurrence; + + /** + * Constructor. + * + * @param status The HTTP status code. + * @param msg The status message. + * @param args Optional string format arguments. + */ + public RestException(int status, String msg, Object...args) { + super(args.length == 0 ? msg : MessageFormat.format(msg, args)); + this.status = status; + } + + /** + * Constructor. + * + * @param status The HTTP status code. + * @param cause The root exception. + */ + public RestException(int status, Throwable cause) { + this(status, cause.getLocalizedMessage()); + initCause(cause); + } + + + /** + * Sets the inner cause for this exception. + * + * @param cause The inner cause. + * @return This object (for method chaining). + */ + @Override /* Throwable */ + public synchronized RestException initCause(Throwable cause) { + super.initCause(cause); + return this; + } + + + /** + * Returns all error messages from all errors in this stack. + * <p> + * Typically useful if you want to render all the error messages in the stack, but don't + * want to render all the stack traces too. + * + * @param scrubForXssVulnerabilities If <jk>true</jk>, replaces <js>'<'</js>, <js>'>'</js>, and <js>'&'</js> characters with spaces. + * @return All error messages from all errors in this stack. + */ + public String getFullStackMessage(boolean scrubForXssVulnerabilities) { + String msg = getMessage(); + StringBuilder sb = new StringBuilder(); + if (msg != null) { + if (scrubForXssVulnerabilities) + msg = msg.replace('<', ' ').replace('>', ' ').replace('&', ' '); + sb.append(msg); + } + Throwable e = getCause(); + while (e != null) { + msg = e.getMessage(); + if (msg != null && scrubForXssVulnerabilities) + msg = msg.replace('<', ' ').replace('>', ' ').replace('&', ' '); + String cls = e.getClass().getSimpleName(); + if (msg == null) + sb.append(MessageFormat.format("\nCaused by ({0})", cls)); + else + sb.append(MessageFormat.format("\nCaused by ({0}): {1}", cls, msg)); + e = e.getCause(); + } + return sb.toString(); + } + + @Override /* Object */ + public int hashCode() { + int i = 0; + Throwable t = this; + while (t != null) { + for (StackTraceElement e : t.getStackTrace()) + i ^= e.hashCode(); + t = t.getCause(); + } + return i; + } + + void setOccurrence(int occurrence) { + this.occurrence = occurrence; + } + + /** + * Returns the number of times this exception occurred on this servlet. + * <p> + * This only gets set if {@link RestServletContext#REST_useStackTraceHashes} is enabled on the servlet. + * + * @return The occurrence number if {@link RestServletContext#REST_useStackTraceHashes} is enabled, or <code>0</code> otherwise. + */ + public int getOccurrence() { + return occurrence; + } + + /** + * Returns the HTTP status code. + * + * @return The HTTP status code. + */ + public int getStatus() { + return status; + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/1b4f98a0/org.apache.juneau.server/src/main/java/org/apache/juneau/server/RestGuard.java ---------------------------------------------------------------------- diff --git a/org.apache.juneau.server/src/main/java/org/apache/juneau/server/RestGuard.java b/org.apache.juneau.server/src/main/java/org/apache/juneau/server/RestGuard.java new file mode 100755 index 0000000..46475ce --- /dev/null +++ b/org.apache.juneau.server/src/main/java/org/apache/juneau/server/RestGuard.java @@ -0,0 +1,99 @@ +/*************************************************************************************************************************** + * 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.server; + +import static javax.servlet.http.HttpServletResponse.*; + +import org.apache.juneau.server.annotation.*; + +/** + * REST method guard. + * + * + * <h6 class='topic'>Description</h6> + * <p> + * Implements a guard mechanism for REST method calls that allows requests to be + * rejected before invocation of the REST method. + * For example, guards can be used to ensure that only administrators can call certain methods. + * <p> + * Guards are applied to REST methods declaratively through the {@link RestResource#guards()} or {@link RestMethod#guards()} annotations. + * <p> + * If multiple guards are specified, ALL guards must pass in order for the request to proceed. + * + * + * <h6 class='topic'>How to implement</h6> + * <p> + * Typically, guards will be used for permissions checking on the user making the request, + * but it can also be used for other purposes like pre-call validation of a request. + * <p> + * Implementers should simply throw a {@link RestException} from the {@link #guard(RestRequest, RestResponse)} + * method to abort processing on the current request. + * <p> + * Guards must implement a no-args constructor. + * + * + * <h6 class='topic'>Example usage</h6> + * <p class='bcode'> + * <jk>public</jk> MyResource <jk>extends</jk> RestServlet { + * + * <jc>// Delete method with guard that only allows Billy to call it.</jc> + * <ja>@RestMethod</ja>(name=<js>"DELETE"</js>, guards=BillyGuard.<jk>class</jk>) + * <jk>public</jk> doDelete(RestRequest req, RestResponse res) <jk>throws</jk> Exception {...} + * } + * </p> + * + * + * <h6 class='topic'>Example implementation</h6> + * <p class='bcode'> + * <jc>// Define a guard that only lets Billy make a request</jc> + * <jk>public</jk> BillyGuard <jk>extends</jk> RestGuard { + * + * <ja>@Override</ja> + * <jk>public boolean</jk> isRequestAllowed(RestRequest req) { + * return req.getUserPrincipal().getName().contains(<js>"Billy"</js>); + * } + * } + * </p> + */ +public abstract class RestGuard { + + /** + * Checks the current HTTP request and throws a {@link RestException} if the guard + * does not permit the request. + * <p> + * By default, throws an <jsf>SC_FORBIDDEN</jsf> exception if {@link #isRequestAllowed(RestRequest)} + * returns <jk>false</jk>. + * <p> + * Subclasses are free to override this method to tailor the behavior of how to handle unauthorized + * requests. + * + * @param req The servlet request. + * @param res The servlet response. + * @throws RestException Thrown to abort processing on current request. + * @return <jk>true</jk> if request can proceed. + * Specify <jk>false</jk> if you're doing something like a redirection to a login page. + */ + public boolean guard(RestRequest req, RestResponse res) throws RestException { + if (! isRequestAllowed(req)) + throw new RestException(SC_FORBIDDEN, "Access denied by guard"); + return true; + } + + /** + * Returns <jk>true</jk> if the specified request can pass through this guard. + * + * @param req The servlet request. + * @return <jk>true</jk> if the specified request can pass through this guard. + */ + public abstract boolean isRequestAllowed(RestRequest req); +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/1b4f98a0/org.apache.juneau.server/src/main/java/org/apache/juneau/server/RestMatcher.java ---------------------------------------------------------------------- diff --git a/org.apache.juneau.server/src/main/java/org/apache/juneau/server/RestMatcher.java b/org.apache.juneau.server/src/main/java/org/apache/juneau/server/RestMatcher.java new file mode 100755 index 0000000..c2513f7 --- /dev/null +++ b/org.apache.juneau.server/src/main/java/org/apache/juneau/server/RestMatcher.java @@ -0,0 +1,76 @@ +/*************************************************************************************************************************** + * 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.server; + +import org.apache.juneau.server.annotation.*; + +/** + * Class used for defining method-level matchers using the {@link RestMethod#matchers()} annotation. + * <p> + * Matchers are used to allow multiple Java methods to handle requests assigned to the same + * URL path pattern, but differing based on some request attribute, such as a specific header value. + * For example, matchers can be used to provide two different methods for handling requests + * from two different client versions. + * <p> + * Java methods with matchers associated with them are always attempted before Java methods + * without matchers. + * This allows a 'default' method to be defined to handle requests where no matchers match. + * <p> + * When multiple matchers are specified on a method, only one matcher is required to match. + * This is opposite from the {@link RestMethod#guards()} annotation, where all guards + * are required to match in order to execute the method. + * + * <h6 class='topic'>Example</h6> + * <p class='bcode'> + * <jk>public class</jk> MyResource <jk>extends</jk> RestServlet { + * + * <ja>@RestMethod</ja>(name=<js>"GET"</js>, path=<js>"/foo"</js>, matchers=IsDNT.<jk>class</jk>) + * <jk>public</jk> Object doGetWithDNT() { + * <jc>// Handle request with Do-Not-Track specified</jc> + * } + * + * <ja>@RestMethod</ja>(name=<js>"GET"</js>, path=<js>"/foo"</js>) + * <jk>public</jk> Object doGetWithoutDNT() { + * <jc>// Handle request without Do-Not-Track specified</jc> + * } + * } + * + * <jk>public class</jk> IsDNT <jk>extends</jk> RestMatcher { + * <ja>@Override</ja> + * <jk>public boolean</jk> matches(RestRequest req) { + * <jk>return</jk> req.getHeader(<jk>int</jk>.<jk>class</jk>, <js>"DNT"</js>, 0) == 1; + * } + * } + * </p> + */ +public abstract class RestMatcher { + + /** + * Returns <jk>true</jk> if the specified request matches this matcher. + * + * @param req The servlet request. + * @return <jk>true</jk> if the specified request matches this matcher. + */ + public abstract boolean matches(RestRequest req); + + /** + * Returns <jk>true</jk> if this matcher is required to match in order for the method to be invoked. + * <p> + * If <jk>false</jk>, then only one of the matchers must match. + * + * @return <jk>true</jk> if this matcher is required to match in order for the method to be invoked. + */ + public boolean mustMatch() { + return false; + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/1b4f98a0/org.apache.juneau.server/src/main/java/org/apache/juneau/server/RestMatcherReflecting.java ---------------------------------------------------------------------- diff --git a/org.apache.juneau.server/src/main/java/org/apache/juneau/server/RestMatcherReflecting.java b/org.apache.juneau.server/src/main/java/org/apache/juneau/server/RestMatcherReflecting.java new file mode 100644 index 0000000..3140559 --- /dev/null +++ b/org.apache.juneau.server/src/main/java/org/apache/juneau/server/RestMatcherReflecting.java @@ -0,0 +1,35 @@ +/*************************************************************************************************************************** + * 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.server; + +import java.lang.reflect.*; + +/** + * Subclass of {@link RestMatcher} that gives access to the servlet and Java method it's applied to. + * <p> + * Essentially the same as {@link RestMatcher} except has a constructor where the + * Java method is passed in so that you can access annotations defined on it to tailor + * the behavior of the matcher. + * + * @author James Bognar ([email protected]) + */ +public abstract class RestMatcherReflecting extends RestMatcher { + + /** + * Constructor. + * + * @param servlet The REST servlet. + * @param javaMethod The Java method that this rest matcher is defined on. + */ + protected RestMatcherReflecting(RestServlet servlet, Method javaMethod) {} +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/1b4f98a0/org.apache.juneau.server/src/main/java/org/apache/juneau/server/RestRequest.java ---------------------------------------------------------------------- diff --git a/org.apache.juneau.server/src/main/java/org/apache/juneau/server/RestRequest.java b/org.apache.juneau.server/src/main/java/org/apache/juneau/server/RestRequest.java new file mode 100755 index 0000000..29ce04d --- /dev/null +++ b/org.apache.juneau.server/src/main/java/org/apache/juneau/server/RestRequest.java @@ -0,0 +1,1764 @@ +/*************************************************************************************************************************** + * 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.server; + +import static java.util.Collections.*; +import static java.util.logging.Level.*; +import static javax.servlet.http.HttpServletResponse.*; + +import java.io.*; +import java.lang.reflect.*; +import java.net.*; +import java.text.*; +import java.util.*; +import java.util.logging.*; + +import javax.servlet.*; +import javax.servlet.http.*; + +import org.apache.juneau.*; +import org.apache.juneau.encoders.*; +import org.apache.juneau.encoders.Encoder; +import org.apache.juneau.ini.*; +import org.apache.juneau.internal.*; +import org.apache.juneau.parser.*; +import org.apache.juneau.parser.ParseException; +import org.apache.juneau.serializer.*; +import org.apache.juneau.server.labels.*; +import org.apache.juneau.server.labels.Var; +import org.apache.juneau.svl.*; +import org.apache.juneau.urlencoding.*; +import org.apache.juneau.utils.*; + +/** + * Represents an HTTP request for a REST resource. + * <p> + * Equivalent to {@link HttpServletRequest} except with some additional convenience methods. + * </p> + * <p> + * For reference, given the URL <js>"http://localhost:9080/contextRoot/servletPath/foo?bar=baz#qux"</js>, the + * following methods return the following values.... + * </p> + * <table class='styled'> + * <tr><th>Method</th><th>Value</th></tr> + * <tr><td>{@code getContextPath()}</td><td>{@code /contextRoot}</td></tr> + * <tr><td>{@code getPathInfo()}</td><td>{@code /foo}</td></tr> + * <tr><td>{@code getPathTranslated()}</td><td>{@code path-to-deployed-war-on-filesystem/foo}</td></tr> + * <tr><td>{@code getQueryString()}</td><td>{@code bar=baz}</td></tr> + * <tr><td>{@code getRequestURI()}</td><td>{@code /contextRoot/servletPath/foo}</td></tr> + * <tr><td>{@code getRequestURL()}</td><td>{@code http://localhost:9080/contextRoot/servletPath/foo}</td></tr> + * <tr><td>{@code getServletPath()}</td><td>{@code /servletPath}</td></tr> + * </table> + * <p> + * Refer to <a class='doclink' href='package-summary.html#TOC'>REST Servlet API</a> for information about using this class. + * </p> + * + * @author jbognar + */ +@SuppressWarnings("unchecked") +public final class RestRequest extends HttpServletRequestWrapper { + + private final RestServlet servlet; + private String method, pathRemainder, content; + Method javaMethod; + private ObjectMap properties; + private SerializerGroup serializerGroup; + private ParserGroup parserGroup; + private Encoder encoder; + private int contentLength; + private final boolean debug; + private UrlEncodingParser urlEncodingParser; // The parser used to parse URL attributes and parameters (beanContext also used to parse headers) + private BeanContext beanContext; + private VarResolverSession varSession; + private Map<String,String[]> queryParams; + private Map<String,String> defaultServletHeaders, defaultMethodHeaders, overriddenHeaders, overriddenParams; + private boolean isPost; + private String servletURI, relativeServletURI; + private String charset, defaultCharset; + private ObjectMap headers; + private ConfigFile cf; + + /** + * Constructor. + */ + RestRequest(RestServlet servlet, HttpServletRequest req) throws ServletException { + super(req); + + try { + this.servlet = servlet; + isPost = req.getMethod().equalsIgnoreCase("POST"); + + // If this is a POST, we want to parse the query parameters ourselves to prevent + // the servlet code from processing the HTTP body as URL-Encoded parameters. + if (isPost) + queryParams = servlet.getUrlEncodingParser().parseIntoSimpleMap(getQueryString()); + else { + queryParams = req.getParameterMap(); + } + + // Get the HTTP method. + // Can be overridden through a "method" GET attribute. + method = super.getMethod(); + + String m = getQueryParameter("method"); + if (! StringUtils.isEmpty(m) && (servlet.context.allowMethodParams.contains(m) || servlet.context.allowMethodParams.contains("*"))) + method = m; + + if (servlet.context.allowContentParam) + content = getQueryParameter("content"); + + defaultServletHeaders = servlet.getDefaultRequestHeaders(); + + debug = "true".equals(getQueryParameter("debug", "false")); + + if (debug) { + servlet.log(Level.INFO, toString()); + } + + } catch (RestException e) { + throw e; + } catch (Exception e) { + throw new ServletException(e); + } + } + + /* + * Called from RestServlet after a match has been made but before the guard or method invocation. + */ + @SuppressWarnings("hiding") + final void init(Method javaMethod, String pathRemainder, ObjectMap properties, Map<String,String> mDefaultRequestHeaders, String defaultCharset, SerializerGroup mSerializers, ParserGroup mParsers, UrlEncodingParser mUrlEncodingParser) { + this.javaMethod = javaMethod; + this.pathRemainder = pathRemainder; + this.properties = properties; + this.defaultMethodHeaders = mDefaultRequestHeaders; + this.serializerGroup = mSerializers; + this.parserGroup = mParsers; + this.urlEncodingParser = mUrlEncodingParser; + this.beanContext = urlEncodingParser.getBeanContext(); + this.defaultCharset = defaultCharset; + } + + /** + * Returns <jk>true</jk> if the request contains any of the specified parameters. + * + * @param params The list of parameters to check for. + * @return <jk>true</jk> if the request contains any of the specified parameters. + */ + public boolean hasAnyQueryParameters(String...params) { + for (String p : params) + if (hasQueryParameter(p)) + return true; + return false; + } + + /** + * Returns a string of the form <js>"HTTP method-name full-url"</js> + * + * @return A description of the request. + */ + public String getDescription() { + String qs = getQueryString(); + return "HTTP " + getMethod() + " " + getRequestURI() + (qs == null ? "" : "?" + qs); + } + + /** + * Servlet calls this method to initialize the properties. + */ + RestRequest setProperties(ObjectMap properties) { + this.properties = properties; + return this; + } + + /** + * Retrieve the properties active for this request. + * These properties can be modified by the request. + * + * @return The properties active for this request. + */ + public ObjectMap getProperties() { + return this.properties; + } + + /** + * Returns the <code>Content-Type</code> header value on the request, stripped + * of any parameters such as <js>";charset=X"</js>. + * <p> + * Example: <js>"text/json"</js>. + * <p> + * If the content type is not specified, and the content is specified via a + * <code>&content</code> query parameter, the content type is assumed to be + * <js>"text/uon"</js>. Otherwise, the + * content type is assumed to be <js>"text/json"</js>. + * + * @return The <code>Accept</code> media-type header values on the request. + */ + public String getMediaType() { + String cm = getHeader("Content-Type"); + if (cm == null) { + if (content != null) + return "text/uon"; + return "text/json"; + } + int j = cm.indexOf(';'); + if (j != -1) + cm = cm.substring(0, j); + return cm; + } + + /** + * Returns the media types that are valid for <code>Content-Type</code> headers on the request. + * + * @return The set of media types registered in the parser group of this request. + */ + public List<String> getSupportedMediaTypes() { + return parserGroup.getSupportedMediaTypes(); + } + + /** + * Returns the charset specified on the <code>Content-Type</code> header, or + * <js>"UTF-8"</js> if not specified. + */ + @Override /* ServletRequest */ + public String getCharacterEncoding() { + if (charset == null) { + // Determine charset + // NOTE: Don't use super.getCharacterEncoding() because the spec is implemented inconsistently. + // Jetty returns the default charset instead of null if the character is not specified on the request. + String h = getHeader("Content-Type"); + if (h != null) { + int i = h.indexOf(";charset="); + if (i > 0) + charset = h.substring(i+9).trim(); + } + if (charset == null) + charset = defaultCharset; + if (! RestServlet.availableCharsets.containsKey(charset)) + throw new RestException(SC_UNSUPPORTED_MEDIA_TYPE, "Unsupported charset in header ''Content-Type'': ''{0}''", h); + } + return charset; + } + + /** + * Sets the charset to expect on the request body. + */ + @Override /* ServletRequest */ + public void setCharacterEncoding(String charset) { + this.charset = charset; + } + + /** + * Returns the specified header value, or <jk>null</jk> if the header value isn't present. + * <p> + * If {@code allowHeaderParams} init parameter is <jk>true</jk>, then first looks + * for {@code &HeaderName=x} in the URL query string. + */ + @Override /* ServletRequest */ + public String getHeader(String name) { + return getHeader(name, (String)null); + } + + /** + * Returns all the request headers as an {@link ObjectMap}. + * <p> + * Altering entries in this map does not alter headers in the underlying request. + * + * @return The request headers. Never <jk>null</jk>. + */ + public ObjectMap getHeaders() { + if (headers == null) { + headers = new ObjectMap(); + for (Enumeration<String> e = getHeaderNames(); e.hasMoreElements();) { + String key = e.nextElement(); + headers.put(key, getHeader(key)); + } + } + return headers; + } + + /** + * Set the request header to the specified value. + * + * @param name The header name. + * @param value The header value. + */ + public void setHeader(String name, String value) { + if (overriddenHeaders == null) + overriddenHeaders = new TreeMap<String,String>(String.CASE_INSENSITIVE_ORDER); + overriddenHeaders.put(name, value); + } + + /** + * Set the request parameter to the specified value. + * + * @param name The parameter name. + * @param value The parameter value. + */ + public void setParameter(String name, Object value) { + if (overriddenParams == null) + overriddenParams = new TreeMap<String,String>(String.CASE_INSENSITIVE_ORDER); + overriddenParams.put(name, value == null ? null : value.toString()); + } + + /** + * Returns the specified header value, or the specified default value if the + * header value isn't present. + * <p> + * If {@code allowHeaderParams} init parameter is <jk>true</jk>, then first looks + * for {@code &HeaderName=x} in the URL query string. + * + * @param name The HTTP header name. + * @param def The default value to return if the header value isn't found. + * @return The header value, or the default value if the header isn't present. + */ + public String getHeader(String name, String def) { + String h = getOverriddenHeader(name); + if (h != null) + return h; + h = super.getHeader(name); + if (h != null && ! h.isEmpty()) + return h; + if (defaultMethodHeaders != null) { + h = defaultMethodHeaders.get(name); + if (h != null) + return h; + } + h = defaultServletHeaders.get(name); + if (h != null) + return h; + return def; + } + + @Override /* ServletRequest */ + public Enumeration<String> getHeaders(String name) { + String h = getOverriddenHeader(name); + if (h != null) + return enumeration(singleton(h)); + return super.getHeaders(name); + } + + /* + * Returns header value from URL-parameters or set via setHeader() meant + * to override actual header values on the request. + */ + private String getOverriddenHeader(String name) { + String h = null; + if (servlet.context.allowHeaderParams) + h = getQueryParameter(name); + if (h != null) + return h; + if (overriddenHeaders != null) { + h = overriddenHeaders.get(name); + if (h != null) + return h; + } + return h; + } + + @Override /* ServletRequest */ + public Locale getLocale() { + String h = getOverriddenHeader("Accept-Language"); + if (h != null) { + MediaRange[] mr = MediaRange.parse(h); + if (mr.length > 0) + return toLocale(mr[0].getType()); + } + return super.getLocale(); + } + + @Override /* ServletRequest */ + public Enumeration<Locale> getLocales() { + String h = getOverriddenHeader("Accept-Language"); + if (h != null) { + MediaRange[] mr = MediaRange.parse(h); + if (mr.length > 0) { + List<Locale> l = new ArrayList<Locale>(mr.length); + for (MediaRange r : mr) + l.add(toLocale(r.getType())); + return enumeration(l); + } + } + return super.getLocales(); + } + + /** + * Converts an Accept-Header value entry to a Locale. + */ + private Locale toLocale(String lang) { + String country = ""; + int i = lang.indexOf('-'); + if (i > -1) { + country = lang.substring(i+1).trim(); + lang = lang.substring(0,i).trim(); + } + return new Locale(lang, country); + } + + /** + * Returns the serializers associated with this request. + * + * @return The serializers associated with this request. + */ + public SerializerGroup getSerializerGroup() { + return serializerGroup; + } + + /** + * Returns the parsers associated with this request. + * + * @return The parsers associated with this request. + */ + public ParserGroup getParserGroup() { + return parserGroup; + } + + /** + * Returns the method of this request. + * <p> + * If <code>allowHeaderParams</code> init parameter is <jk>true</jk>, then first looks + * for <code>&method=xxx</code> in the URL query string. + */ + @Override /* ServletRequest */ + public String getMethod() { + return method; + } + + /** + * Returns the parameter with the specified name. + * <p> + * Returns <jk>null</jk> for parameters with no value (e.g. <js>"&foo"</js>). + * This is consistent with WAS, but differs from Tomcat behavior. + * The presence of parameter <js>"&foo"</js> in this case can be determined using {@link #hasParameter(String)}. + * <p> + * Parameter lookup is case-insensitive (consistent with WAS, but differs from Tomcat). + * <p> + * <i>Note:</i> Calling this method on URL-Encoded FORM posts causes the body content to be loaded and parsed by + * the underlying servlet API. + * <p> + * <i>Note:</i> This method returns the raw unparsed value, and differs from calling <code>getParameter(name, String.<jk>class</js>)</code> + * which will convert the value from UON notation: + * <ul> + * <li><js>"\u0000"</js> => <jk>null</jk> + * <li><js>"$s(foo)"</js> => <js>"foo"</js> + * <li><js>"(foo)"</js> => <js>"foo"</js> + * </ul> + */ + @Override /* ServletRequest */ + public String getParameter(String name) { + String s = null; + if (overriddenParams != null) + s = overriddenParams.get(name); + if (s != null) + return s; + + String val = super.getParameter(name); + + // Fix for behavior difference between Tomcat and WAS. + // getParameter("foo") on "&foo" in Tomcat returns "". + // getParameter("foo") on "&foo" in WAS returns null. + if (val != null && val.isEmpty()) + if (queryParams.containsKey(name)) + val = null; + + return val; + } + + /** + * Same as {@link #getParameter(String)} except returns the default value + * if <jk>null</jk> or empty. + * + * @param name The query parameter name. + * @param def The default value. + * @return The parameter value, or the default value if <jk>null</jk> or empty. + */ + public String getParameter(String name, String def) { + String val = getParameter(name); + if (val == null || val.isEmpty()) + return def; + return val; + } + + /** + * Returns the specified URL parameter value parsed to the specified class type using the + * {@link UrlEncodingParser} registered with this servlet. + * <p> + * <i>Note:</i> Calling this method on URL-Encoded FORM posts causes the body content to be loaded and parsed by + * the underlying servlet API. + * + * @param name The parameter name. + * @param c The class type to convert the parameter value to. + * @param def The default value if the parameter was not specified or is <jk>null</jk>. + * @param <T> The class type to convert the parameter value to. + * @return The parameter value converted to the specified class type. + * @throws ParseException + */ + public <T> T getParameter(String name, Class<T> c, T def) throws ParseException { + return getParameter(name, beanContext.getClassMeta(c), def); + } + + /** + * Returns the specified URL parameter value parsed to the specified class type using the + * {@link UrlEncodingParser} registered with this servlet. + * <p> + * <i>Note:</i> Calling this method on URL-Encoded FORM posts causes the body content to be loaded and parsed by + * the underlying servlet API. + * <p> + * Unlike {@link #getParameter(String, Class, Object)}, this method can be used to parse parameters + * of complex types involving JCF classes. + * <p class='bcode'> + * ClassMeta<Map<String,Integer>> cm = request.getBeanContext().getMapClassMeta(TreeMap.<jk>class</jk>, String.<jk>class</jk>, Integer.<jk>class</jk>); + * Map<String,Integer> m = request.getParameter(<js>"myParameter"</js>, cm, <jk>new</jk> TreeMap<String,Integer>()); + * </p> + * + * @param name The parameter name. + * @param cm The class type to convert the parameter value to. + * @param def The default value if the parameter was not specified or is <jk>null</jk>. + * @param <T> The class type to convert the parameter value to. + * @return The parameter value converted to the specified class type. + * @throws ParseException + */ + public <T> T getParameter(String name, ClassMeta<T> cm, T def) throws ParseException { + String val = getParameter(name); + if (val == null) + return def; + return parseParameter(val, cm); + } + + /** + * Returns the specified URL parameter value parsed to the specified class type using the + * {@link UrlEncodingParser} registered with this servlet. + * <p> + * <i>Note:</i> Calling this method on URL-Encoded FORM posts causes the body content to be loaded and parsed by + * the underlying servlet API. + * + * @param name The parameter name. + * @param c The class type to convert the parameter value to. + * @param <T> The class type to convert the parameter value to. + * @return The parameter value converted to the specified class type. + * @throws ParseException + */ + public <T> T getParameter(String name, Class<T> c) throws ParseException { + return getParameter(name, beanContext.getClassMeta(c)); + } + + /** + * Same as {@link #getParameter(String, Class)} except for use on multi-part parameters + * (e.g. <js>"&key=1&key=2&key=3"</js> instead of <js>"&key=(1,2,3)"</js>) + * <p> + * This method must only be called when parsing into classes of type Collection or array. + * + * @param name The parameter name. + * @param c The class type to convert the parameter value to. + * @return The parameter value converted to the specified class type. + * @throws ParseException + */ + public <T> T getParameters(String name, Class<T> c) throws ParseException { + return getParameters(name, beanContext.getClassMeta(c)); + } + + /** + * Same as {@link #getParameter(String, Class)} except works on parameterized + * types such as those returned by {@link Method#getGenericParameterTypes()} + * + * @param name The parameter name. + * @param c The class type to convert the parameter value to. + * @return The parameter value converted to the specified class type. + * @throws ParseException + */ + public <T> T getParameter(String name, Type c) throws ParseException { + return (T)getParameter(name, beanContext.getClassMeta(c)); + } + + /** + * Same as {@link #getParameter(String, Class)} except for use on multi-part parameters + * (e.g. <js>"&key=1&key=2&key=3"</js> instead of <js>"&key=(1,2,3)"</js>) + * <p> + * This method must only be called when parsing into classes of type Collection or array. + * + * @param name The parameter name. + * @param c The class type to convert the parameter value to. + * @return The parameter value converted to the specified class type. + * @throws ParseException + */ + public <T> T getParameters(String name, Type c) throws ParseException { + return (T)getParameters(name, beanContext.getClassMeta(c)); + } + + /** + * Returns the specified URL parameter value parsed to the specified class type using the + * {@link UrlEncodingParser} registered with this servlet. + * <p> + * <i>Note:</i> Calling this method on URL-Encoded FORM posts causes the body content to be loaded and parsed by + * the underlying servlet API. + * <p> + * Unlike {@link #getParameter(String, Class)}, this method can be used to parse parameters + * of complex types involving JCF classes. + * <p class='bcode'> + * ClassMeta<Map<String,Integer>> cm = request.getBeanContext().getMapClassMeta(TreeMap.<jk>class</jk>, String.<jk>class</jk>, Integer.<jk>class</jk>); + * Map<String,Integer> m = request.getParameter(<js>"myParameter"</js>, cm); + * </p> + * + * @param name The parameter name. + * @param cm The class type to convert the parameter value to. + * @param <T> The class type to convert the parameter value to. + * @return The parameter value converted to the specified class type. + * @throws ParseException + */ + public <T> T getParameter(String name, ClassMeta<T> cm) throws ParseException { + + String val = getParameter(name); + + if (cm.isPrimitive() && (val == null || val.isEmpty())) + return cm.getPrimitiveDefault(); + + return parseParameter(val, cm); + } + + /** + * Same as {@link #getParameter(String, ClassMeta)} except for use on multi-part parameters + * (e.g. <js>"&key=1&key=2&key=3"</js> instead of <js>"&key=(1,2,3)"</js>) + * <p> + * This method must only be called when parsing into classes of type Collection or array. + * + * @param name The parameter name. + * @param cm The class type to convert the parameter value to. + * @param <T> The class type to convert the parameter value to. + * @return The parameter value converted to the specified class type. + * @throws ParseException + */ + @SuppressWarnings("rawtypes") + public <T> T getParameters(String name, ClassMeta<T> cm) throws ParseException { + String[] p = getParameterValues(name); + if (p == null) + return null; + if (cm.isArray()) { + List c = new ArrayList(); + for (int i = 0; i < p.length; i++) + c.add(parseParameter(p[i], cm.getElementType())); + return (T)ArrayUtils.toArray(c, cm.getElementType().getInnerClass()); + } else if (cm.isCollection()) { + try { + Collection c = (Collection)(cm.canCreateNewInstance() ? cm.newInstance() : new ObjectList()); + for (int i = 0; i < p.length; i++) + c.add(parseParameter(p[i], cm.getElementType())); + return (T)c; + } catch (ParseException e) { + throw e; + } catch (Exception e) { + // Typically an instantiation exception. + throw new ParseException(e); + } + } + throw new ParseException("Invalid call to getParameters(String, ClassMeta). Class type must be a Collection or array."); + } + + /** + * Returns <jk>true</jk> if the URL parameters on this request contains the specified entry. + * <p> + * Note that this returns <jk>true</jk> even if the value is set to null (e.g. <js>"?key"</js>). + * + * @param name The URL parameter name. + * @return <jk>true</jk> if the URL parameters on this request contains the specified entry. + */ + public boolean hasParameter(String name) { + return getParameterMap().containsKey(name); + } + + /** + * Same as {@link #getParameter(String)} except only looks in the URL string, + * not parameters from URL-Encoded FORM posts. + * <p> + * This method can be used to retrieve a parameter without triggering the underlying + * servlet API to load and parse the request body. + * + * @param name The URL parameter name. + * @return The parameter value, or <jk>null</jk> if parameter not specified or has no value (e.g. <js>"&foo"</js>. + */ + public String getQueryParameter(String name) { + String s = null; + if (overriddenParams != null) + s = overriddenParams.get(name); + if (s != null) + return s; + String[] v = queryParams.get(name); + if (v == null || v.length == 0) + return null; + if (v.length == 1 && v[0] != null && v[0].isEmpty()) { + // Fix for behavior difference between Tomcat and WAS. + // getParameter("foo") on "&foo" in Tomcat returns "". + // getParameter("foo") on "&foo" in WAS returns null. + if (queryParams.containsKey(name)) + return null; + } + + return v[0]; + } + + /** + * Same as {@link #getQueryParameter(String)} but returns the specified default + * value if the query parameter was not specified. + * + * @param name The URL parameter name. + * @param def The default value. + * @return The parameter value, or the default value if parameter not specified or has no value (e.g. <js>"&foo"</js>. + */ + public String getQueryParameter(String name, String def) { + String s = getQueryParameter(name); + return s == null ? def : s; + } + + /** + * Same as {@link #getParameter(String, Class, Object)} except only looks in the URL string, + * not parameters from URL-Encoded FORM posts. + * <p> + * This method can be used to retrieve a parameter without triggering the underlying + * servlet API to load and parse the request body. + * + * @param name The parameter name. + * @param c The class type to convert the parameter value to. + * @param def The default value if the parameter was not specified or is <jk>null</jk>. + * @param <T> The class type to convert the parameter value to. + * @return The parameter value converted to the specified class type. + * @throws ParseException + */ + public <T> T getQueryParameter(String name, Class<T> c, T def) throws ParseException { + return getQueryParameter(name, beanContext.getClassMeta(c), def); + } + + /** + * Same as {@link #getParameter(String, ClassMeta, Object)} except only looks in the URL string, + * not parameters from URL-Encoded FORM posts. + * <p> + * This method can be used to retrieve a parameter without triggering the underlying + * servlet API to load and parse the request body. + * + * @param name The parameter name. + * @param cm The class type to convert the parameter value to. + * @param def The default value if the parameter was not specified or is <jk>null</jk>. + * @param <T> The class type to convert the parameter value to. + * @return The parameter value converted to the specified class type. + * @throws ParseException + */ + public <T> T getQueryParameter(String name, ClassMeta<T> cm, T def) throws ParseException { + String val = getQueryParameter(name); + if (val == null) + return def; + return parseParameter(val, cm); + } + + /** + * Same as {@link #getParameter(String, ClassMeta, Object)} except only looks in the URL string, + * not parameters from URL-Encoded FORM posts. + * <p> + * This method can be used to retrieve a parameter without triggering the underlying + * servlet API to load and parse the request body. + * + * @param name The parameter name. + * @param c The class type to convert the parameter value to. + * @param <T> The class type to convert the parameter value to. + * @return The parameter value converted to the specified class type. + * @throws ParseException + */ + public <T> T getQueryParameter(String name, Class<T> c) throws ParseException { + return getQueryParameter(name, beanContext.getClassMeta(c)); + } + + /** + * Same as {@link #getQueryParameter(String, Class)} except for use on multi-part parameters + * (e.g. <js>"&key=1&key=2&key=3"</js> instead of <js>"&key=(1,2,3)"</js>). + * <p> + * This method must only be called when parsing into classes of type Collection or array. + * + * @param name The query parameter name. + * @param c The class type to convert the parameter value to. + * @param <T> The class type to convert the parameter value to. + * @return The query parameter value converted to the specified class type. + * @throws ParseException + */ + public <T> T getQueryParameters(String name, Class<T> c) throws ParseException { + return getQueryParameters(name, beanContext.getClassMeta(c)); + } + + /** + * Same as {@link #getQueryParameter(String, Class)} except works on parameterized + * types such as those returned by {@link Method#getGenericParameterTypes()} + * + * @param name The query parameter name. + * @param c The class type to convert the parameter value to. + * @param <T> The class type to convert the parameter value to. + * @return The query parameter value converted to the specified class type. + * @throws ParseException + */ + public <T> T getQueryParameter(String name, Type c) throws ParseException { + return (T)getQueryParameter(name, beanContext.getClassMeta(c)); + } + + /** + * Same as {@link #getQueryParameter(String, Type)} except for use on multi-part parameters + * (e.g. <js>"&key=1&key=2&key=3"</js> instead of <js>"&key=(1,2,3)"</js>). + * <p> + * This method must only be called when parsing into classes of type Collection or array. + * + * @param name The query parameter name. + * @param c The class type to convert the parameter value to. + * @param <T> The class type to convert the parameter value to. + * @return The query parameter value converted to the specified class type. + * @throws ParseException + */ + public <T> T getQueryParameters(String name, Type c) throws ParseException { + return (T)getQueryParameters(name, beanContext.getClassMeta(c)); + } + + /** + * Same as {@link #getParameter(String, ClassMeta)} except only looks in the URL string, + * not parameters from URL-Encoded FORM posts. + * <p> + * This method can be used to retrieve a parameter without triggering the underlying + * servlet API to load and parse the request body. + * + * @param name The parameter name. + * @param cm The class type to convert the parameter value to. + * @param <T> The class type to convert the parameter value to. + * @return The parameter value converted to the specified class type. + * @throws ParseException + */ + public <T> T getQueryParameter(String name, ClassMeta<T> cm) throws ParseException { + + String val = getQueryParameter(name); + + if (cm.isPrimitive() && (val == null || val.isEmpty())) + return cm.getPrimitiveDefault(); + return parseParameter(val, cm); + } + + /** + * Same as {@link #getQueryParameter(String, ClassMeta)} except for use on multi-part parameters + * (e.g. <js>"&key=1&key=2&key=3"</js> instead of <js>"&key=(1,2,3)"</js>). + * <p> + * This method must only be called when parsing into classes of type Collection or array. + * + * @param name The parameter name. + * @param cm The class type to convert the parameter value to. + * @param <T> The class type to convert the parameter value to. + * @return The parameter value converted to the specified class type. + * @throws ParseException + */ + @SuppressWarnings("rawtypes") + public <T> T getQueryParameters(String name, ClassMeta<T> cm) throws ParseException { + String[] p = getQueryParameters(name); + if (p == null) + return null; + if (cm.isArray()) { + List c = new ArrayList(); + for (int i = 0; i < p.length; i++) + c.add(parseParameter(p[i], cm.getElementType())); + return (T)ArrayUtils.toArray(c, cm.getElementType().getInnerClass()); + } else if (cm.isCollection()) { + try { + Collection c = (Collection)(cm.canCreateNewInstance() ? cm.newInstance() : new ObjectList()); + for (int i = 0; i < p.length; i++) + c.add(parseParameter(p[i], cm.getElementType())); + return (T)c; + } catch (ParseException e) { + throw e; + } catch (Exception e) { + // Typically an instantiation exception. + throw new ParseException(e); + } + } + throw new ParseException("Invalid call to getQueryParameters(String, ClassMeta). Class type must be a Collection or array."); + } + + /** + * Returns the list of all query parameters with the specified name. + * Same as {@link #getParameterValues(String)} except only looks in the URL string, + * not parameters from URL-Encoded FORM posts. + * <p> + * This method can be used to retrieve parameters without triggering the underlying + * servlet API to load and parse the request body. + * + * @param name + * @return the list of query parameters, or <jk>null</jk> if the parameter does not exist. + */ + public String[] getQueryParameters(String name) { + return queryParams.get(name); + } + + /** + * Returns <jk>true</jk> if the URL parameters on this request contains the specified entry. + * <p> + * Note that this returns <jk>true</jk> even if the value is set to null (e.g. <js>"?key"</js>). + * <p> + * This method can be used to check the existence of a parameter without triggering the underlying + * servlet API to load and parse the request body. + * + * @param name The URL parameter name. + * @return <jk>true</jk> if the URL parameters on this request contains the specified entry. + */ + public boolean hasQueryParameter(String name) { + return queryParams.containsKey(name); + } + + /** + * Equivalent to {@link #getParameterMap()}, but only looks for query parameters in the URL, not form posts. + * <p> + * This method can be used to retrieve query parameters without triggering the underlying + * servlet API to load and parse the request body. + * <p> + * This object is modifiable. + * + * @return The query parameters as a modifiable map. + */ + public Map<String,String[]> getQueryParameterMap() { + return queryParams; + } + + /** + * Equivalent to {@link #getParameterNames()}, but only looks for query parameters in the URL, not form posts. + * <p> + * This method can be used to retrieve query parameters without triggering the underlying + * servlet API to load and parse the request body. + * <p> + * This object is modifiable. + * + * @return An iterator of query parameter names. + */ + public Iterator<String> getQueryParameterNames() { + return queryParams.keySet().iterator(); + } + + private <T> T parseParameter(String val, ClassMeta<T> c) throws ParseException { + if (val == null) + return null; + // Shortcut - If we're returning a string and the value doesn't start with '$' or '(', then + // just return the string since it's a plain value. + if (c.getInnerClass() == String.class && val.length() > 0) { + char x = val.charAt(0); + if (x != '(' && x != '$' && x != '\u0000' && val.indexOf('~') == -1) + return (T)val; + } + return urlEncodingParser.parseParameter(val, c); + } + + /** + * Shortcut for calling <code>getHeaders().get(c, name, def);</code> + * <p> + * The type can be any POJO type convertable from a <code>String</code> (See <a class='doclink' href='package-summary.html#PojosConvertableFromString'>POJOs Convertable From Strings</a>). + * + * @param name The HTTP header name. + * @param c The class type to convert the header value to. + * @param def The default value if the header was not specified or is <jk>null</jk>. + * @param <T> The class type to convert the header value to. + * @return The parameter value converted to the specified class type. + */ + public <T> T getHeader(String name, Class<T> c, T def) { + String h = getHeader(name); + if (h == null) + return def; + return beanContext.convertToType(h, c); + } + + /** + * Shortcut for calling <code>getHeaders().get(c, name);</code> + * <p> + * The type can be any POJO type convertable from a <code>String</code> (See <a class='doclink' href='package-summary.html#PojosConvertableFromString'>POJOs Convertable From Strings</a>). + * + * @param name The HTTP header name. + * @param c The class type to convert the header value to. + * @param <T> The class type to convert the header value to. + * @return The parameter value converted to the specified class type. + */ + public <T> T getHeader(String name, Class<T> c) { + String h = getHeader(name); + return beanContext.convertToType(h, c); + } + + /** + * Same as {@link #getHeader(String, Class)} except works on parameterized + * types such as those returned by {@link Method#getGenericParameterTypes()} + * + * @param name The HTTP header name. + * @param c The class type to convert the header value to. + * @param <T> The class type to convert the header value to. + * @return The parameter value converted to the specified class type. + */ + public <T> T getHeader(String name, Type c) { + String h = getHeader(name); + return (T)beanContext.convertToType(null, h, beanContext.getClassMeta(c)); + } + + /** + * Returns the specified request attribute converted to the specified class type. + * <p> + * The type can be any POJO type convertable from a <code>String</code> (See <a class='doclink' href='package-summary.html#PojosConvertableFromString'>POJOs Convertable From Strings</a>). + * + * @param name The attribute name. + * @param c The class type to convert the attribute value to. + * @param <T> The class type to convert the attribute value to. + * @return The attribute value converted to the specified class type. + * @throws ParseException + */ + public <T> T getAttribute(String name, Class<T> c) throws ParseException { + return getAttribute(name, beanContext.getClassMeta(c)); + } + + /** + * Same as {@link #getAttribute(String, Class)} except works on parameterized + * types such as those returned by {@link Method#getGenericParameterTypes()} + * + * @param name The attribute name. + * @param c The class type to convert the attribute value to. + * @param <T> The class type to convert the attribute value to. + * @return The attribute value converted to the specified class type. + * @throws ParseException + */ + public <T> T getAttribute(String name, Type c) throws ParseException { + return (T)getAttribute(name, beanContext.getClassMeta(c)); + } + + /** + * Returns the specified request attribute converted to the specified class type. + * <p> + * The type can be any POJO type convertable from a <code>String</code> (See <a class='doclink' href='package-summary.html#PojosConvertableFromString'>POJOs Convertable From Strings</a>). + * + * @param name The attribute name. + * @param cm The class type to convert the attribute value to. + * @param <T> The class type to convert the attribute value to. + * @return The attribute value converted to the specified class type. + * @throws ParseException + */ + public <T> T getAttribute(String name, ClassMeta<T> cm) throws ParseException { + Object attr = getAttribute(name); + T t = null; + if (attr != null) + t = urlEncodingParser.parseParameter(attr.toString(), cm); + if (t == null && cm.isPrimitive()) + return cm.getPrimitiveDefault(); + return t; + } + + /** + * Same as {@link HttpServletRequest#getPathInfo()} except returns the path undecoded. + * + * @return The undecoded portion of the URL after the resource URL path pattern match. + */ + public String getPathInfoUndecoded() { + return RestUtils.getPathInfoUndecoded(this); + } + + /** + * Returns the value {@link #getPathInfo()} split on the <js>'/'</js> character. + * <p> + * If path info is <jk>null</jk>, returns an empty list. + * <p> + * URL-encoded characters in segments are automatically decoded by this method. + * + * @return The decoded segments, or an empty list if path info is <jk>null</jk>. + */ + public String[] getPathInfoParts() { + String s = getPathInfoUndecoded(); + if (s == null || s.isEmpty() || s.equals("/")) + return new String[0]; + s = s.substring(1); + if (s.endsWith("/")) + s = s.substring(0, s.length()-1); + boolean needsDecode = (s.indexOf('%') != -1 || s.indexOf('+') != -1); + String[] l = s.split("/", Integer.MAX_VALUE); + try { + if (needsDecode) + for (int i = 0; i < l.length; i++) + l[i] = URLDecoder.decode(l[i], "UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); // Won't happen. + } + return l; + } + + /** + * Same as {@link #getInput(ClassMeta)}, except a shortcut for passing in regular {@link Class} objects + * instead of having to look up {@link ClassMeta} objects. + * + * @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 getInput(Class<T> type) throws IOException, ParseException { + return getInput(beanContext.getClassMeta(type)); + } + + /** + * Same as {@link #getInput(Class)} except works on parameterized + * types such as those returned by {@link Method#getGenericParameterTypes()} + * + * @param type The class type to instantiate. + * @param <T> The class type to instantiate. + * @return The input parsed to a POJO. + */ + public <T> T getInput(Type type) { + return (T)getInput(beanContext.getClassMeta(type)); + } + + /** + * Reads the input from the HTTP request as JSON, XML, or HTML and converts the input to the specified class type. + * <p> + * If {@code allowHeaderParams} init parameter is <jk>true</jk>, then first looks + * for {@code &content=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 href='../core/package-summary.html#PojoCategories' class='doclink'>POJO Categories</a> for a complete definition of supported POJOs. + * + * @param type The class type to instantiate. + * @param <T> The class type to instantiate. + * @return The input parsed to a POJO. + * @throws RestException If a problem occurred trying to read the input. + */ + public <T> T getInput(ClassMeta<T> type) throws RestException { + + try { + if (type.isReader()) + return (T)getReader(); + + if (type.isInputStream()) + return (T)getInputStream(); + + String mediaType = getMediaType(); + Parser p = getParser(); + + if (p != null) { + try { + properties.append("mediaType", mediaType).append("characterEncoding", getCharacterEncoding()); + if (! p.isReaderParser()) { + InputStreamParser p2 = (InputStreamParser)p; + ParserSession session = p2.createSession(getInputStream(), properties, getJavaMethod(), getServlet()); + return p2.parse(session, type); + } + ReaderParser p2 = (ReaderParser)p; + ParserSession session = p2.createSession(getUnbufferedReader(), properties, getJavaMethod(), getServlet()); + return p2.parse(session, type); + } catch (ParseException e) { + throw new RestException(SC_BAD_REQUEST, + "Could not convert request body content to class type ''{0}'' using parser ''{1}''.", + type, 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}", + getHeader("Content-Type"), parserGroup.getSupportedMediaTypes() + ); + + } catch (IOException e) { + throw new RestException(SC_INTERNAL_SERVER_ERROR, + "I/O exception occurred while attempting to handle request ''{0}''.", + getDescription() + ).initCause(e); + } + } + + /** + * Returns the parser matching the request <code>Accept</code> header. + * + * @return The parser matching the request <code>Accept</code> header, or <jk>null</jk> + * if no matching parser was found. + */ + public Parser getParser() { + String mediaType = getMediaType(); + Parser p = parserGroup.getParser(mediaType); + + // If no patching parser for URL-encoding, use the one defined on the servlet. + if (p == null && mediaType.equals("application/x-www-form-urlencoded")) + p = urlEncodingParser; + + return p; + } + + /** + * Returns the reader parser matching the request <code>Accept</code> header. + * + * @return The reader parser matching the request <code>Accept</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.isReaderParser()) + return (ReaderParser)p; + return null; + } + + /** + * Returns the HTTP body content as a plain string. + * <p> + * If {@code allowHeaderParams} init parameter is true, then first looks + * for {@code &content=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 getInputAsString() throws IOException { + if (content != null) + return content; + content = IOUtils.read(getReader()).toString(); + return content; + } + + /** + * Returns a resolved URL. + * <p> + * <ul class='spaced-list'> + * <li>Fully-qualified absolute URLs (e.g. <js>"http://..."</js>, <js>"https://"</js>) are simply converted to a URL. + * <li>Absolute URLs (e.g. <js>"/foo/..."</js>) are interpreted as relative to the server hostname. + * <li>Relative URLs (e.g. <js>"foo/..."</js>) are interpreted as relative to this servlet path. + * </ul> + * + * @param path The URL path to resolve. + * @return The resolved URL. + * @throws MalformedURLException If path is not a valid URL component. + */ + public URL getURL(String path) throws MalformedURLException { + if (path.startsWith("http://") || path.startsWith("https://")) + return new URL(path); + if (StringUtils.startsWith(path, '/')) + return new URL(getScheme(), getLocalName(), getLocalPort(), path); + return new URL(getScheme(), getLocalName(), getLocalPort(), getContextPath() + getServletPath() + (StringUtils.isEmpty(path) ? "" : ('/' + path))); + } + + /** + * Returns the HTTP body content as a {@link Reader}. + * <p> + * If {@code allowHeaderParams} init parameter is true, then first looks + * for {@code &content=xxx} in the URL query string. + * <p> + * Automatically handles GZipped input streams. + */ + @Override /* ServletRequest */ + public BufferedReader getReader() throws IOException { + Reader r = getUnbufferedReader(); + if (r instanceof BufferedReader) + return (BufferedReader)r; + int len = 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 (content != null) + return new CharSequenceReader(content); + return new InputStreamReader(getInputStream(), 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. + */ + @Override /* ServletRequest */ + public ServletInputStream getInputStream() throws IOException { + + Encoder enc = getEncoder(); + + ServletInputStream is = super.getInputStream(); + if (enc != null) { + final InputStream is2 = enc.getInputStream(is); + return new ServletInputStream() { + @Override /* InputStream */ + public final int read() throws IOException { + return is2.read(); + } + @Override /* InputStream */ + public final void close() throws IOException { + is2.close(); + } + }; + } + return is; + } + + private Encoder getEncoder() { + if (encoder == null) { + String ce = getHeader("content-encoding"); + if (! (ce == null || ce.isEmpty())) { + ce = ce.trim(); + encoder = servlet.getEncoders().getEncoder(ce); + if (encoder == null) + throw new RestException(SC_UNSUPPORTED_MEDIA_TYPE, + "Unsupported encoding in request header ''Content-Encoding'': ''{0}''\n\tSupported codings: {1}", + getHeader("content-encoding"), servlet.getEncoders().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; + } + + @Override /* ServletRequest */ + public int getContentLength() { + return contentLength == 0 ? super.getContentLength() : contentLength; + } + + /** + * Returns <jk>true</jk> if <code>&plainText=true</code> was specified as a URL parameter. + * <p> + * This indicates that the <code>Content-Type</code> of the output should always be set to <js>"text/plain"</js> + * to make it easy to render in a browser. + * <p> + * This feature is useful for debugging. + * + * @return <jk>true</jk> if {@code &plainText=true} was specified as a URL parameter + */ + public boolean isPlainText() { + return "true".equals(getQueryParameter("plainText", "false")); + } + + /** + * Returns the decoded remainder of the URL following any path pattern matches. + * <p> + * The behavior of path remainder is shown below given the path pattern "/foo/*": + * <p> + * <table class='styled'> + * <tr> + * <th>URL</th> + * <th>Path Remainder</th> + * </tr> + * <tr> + * <th><code>/foo</code></th> + * <th><jk>null</jk></th> + * </tr> + * <tr> + * <th><code>/foo/</code></th> + * <th><js>""</js></th> + * </tr> + * <tr> + * <th><code>/foo//</code></th> + * <th><js>"/"</js></th> + * </tr> + * <tr> + * <th><code>/foo///</code></th> + * <th><js>"//"</js></th> + * </tr> + * <tr> + * <th><code>/foo/a/b</code></th> + * <th><js>"a/b"</js></th> + * </tr> + * <tr> + * <th><code>/foo//a/b/</code></th> + * <th><js>"/a/b/"</js></th> + * </tr> + * <tr> + * <th><code>/foo/a%2Fb</code></th> + * <th><js>"a/b"</js></th> + * </tr> + * </table> + * + * <dl> + * <dt>Example:</dt> + * <dd> + * <p class='bcode'> + * <jc>// REST method</jc> + * <ja>@RestMethod</ja>(name=<js>"GET"</js>,path=<js>"/foo/{bar}/*"</js>) + * <jk>public</jk> doGetById(RestServlet res, RestResponse res, <jk>int</jk> bar) { + * System.<jsm>err</jsm>.println(res.getRemainder()); + * } + * + * <jc>// Prints "path/remainder"</jc> + * <jk>new</jk> RestCall(servletPath + <js>"/foo/123/path/remainder"</js>).connect(); + * </p> + * </dd> + * </dl> + * + * @return The path remainder string. + */ + public String getPathRemainder() { + return RestUtils.decode(pathRemainder); + } + + /** + * Same as {@link #getPathRemainder()} but doesn't decode characters. + * + * @return The undecoded path remainder. + */ + public String getPathRemainderUndecoded() { + return pathRemainder; + } + + /** + * Shortcut method for calling {@link RestServlet#getMessage(Locale, String, Object...)} based + * on the request locale. + * + * @param key The message key. + * @param args Optional {@link MessageFormat} variable values in the value. + * @return The localized message. + */ + public String getMessage(String key, Object...args) { + return servlet.getMessage(getLocale(), key, args); + } + + /** + * Shortcut method for calling {@link RestServlet#getMethodDescriptions(RestRequest)} based + * on the request locale. + * + * @return The localized method descriptions. + * @throws RestServletException + */ + public Collection<MethodDescription> getMethodDescriptions() throws RestServletException { + return servlet.getMethodDescriptions(this); + } + + /** + * Returns the resource bundle for the request locale. + * + * @return The resource bundle. Never <jk>null</jk>. + */ + public MessageBundle getResourceBundle() { + return servlet.getMessages(getLocale()); + } + + /** + * Returns the servlet handling the request. + * <p> + * Can be used to access servlet-init parameters or annotations during requests, + * such as in calls to {@link RestGuard#guard(RestRequest, RestResponse)}.. + * + * @return The servlet handling the request. + */ + public RestServlet getServlet() { + return servlet; + } + + /** + * Returns the java method handling the request. + * <p> + * Can be used to access the method name or method annotations during requests, such + * as in calls to {@link RestGuard#guard(RestRequest, RestResponse)}. + * <p> + * Note: This returns null when evaluating servlet-level guards since the method + * has not been resolved at that point of execution. + * + * @return The Java method handling the request, or <code>null</code> if the method + * has not yet been resolved. + */ + public Method getJavaMethod() { + return javaMethod; + } + + /** + * Returns the URI of the parent resource. + * <p> + * Trailing slashes in the path are ignored by this method. + * <p> + * The behavior is shown below: + * <table class='styled'> + * <tr> + * <th>getRequestURI</th> + * <th>getRequestParentURI</th> + * </tr> + * <tr> + * <th><code>/foo/bar</code></th> + * <th><code>/foo</code></th> + * </tr> + * <tr> + * <th><code>/foo/bar?baz=bing</code></th> + * <th><code>/foo</code></th> + * </tr> + * <tr> + * <th><code>/foo/bar/</code></th> + * <th><code>/foo</code></th> + * </tr> + * <tr> + * <th><code>/foo/bar//</code></th> + * <th><code>/foo</code></th> + * </tr> + * <tr> + * <th><code>/foo//bar//</code></th> + * <th><code>/foo/</code></th> + * </tr> + * <tr> + * <th><code>/foo</code></th> + * <th>/</th> + * </tr> + * </table> + * + * @return The request parent URI. + */ + public String getRequestParentURI() { + String uri = getRequestURI(); + while (StringUtils.endsWith(uri, '/')) + uri = uri.substring(0, uri.length()-1); + int i = uri.lastIndexOf('/'); + if (i <= 0) + return "/"; + return uri.substring(0, i); + } + + /** + * Same as {@link #getRequestURI()} but trims trailing slashes from the result. + * + * @return The trimmed request URI. + */ + public String getTrimmedRequestURI() { + return RestUtils.trimTrailingSlashes(getRequestURI()); + } + + /** + * Same as {@link #getRequestURL()} but trims trailing slashes from the result. + * + * @return The trimmed request URL. + */ + public StringBuffer getTrimmedRequestURL() { + return RestUtils.trimTrailingSlashes(getRequestURL()); + } + + /** + * Gets the URI of the servlet (e.g. <js>"https://localhost:9080/contextPath/servletPath"</js>). + * + * @return The servlet URI. + */ + public String getServletURI() { + if (servletURI == null) { + // Note that we can't use getPathInfo() to calculate this since it replaces + // URL-encoded chars (e.g. %2F) which throws off the length calculation + // because getRequestURL() does not replace those chars. + servletURI = getServletURIBuilder().toString(); + } + return servletURI; + } + + /** + * Gets the path-absolute relative URI of the servlet (e.g. <js>"/contextPath/servletPath"</js>). + * + * @return The relative servlet URI. + */ + public String getRelativeServletURI() { + if (relativeServletURI == null) + relativeServletURI = getContextPath() + getServletPath(); + return relativeServletURI; + } + + /** + * Returns a <code>StringBuffer</code> prefilled with the string <code><js>"/[contextPath]/[servletPath]"</js></code>. + * + * @return The servlet URI string builder. + */ + public StringBuffer getServletURIBuilder() { + return RestUtils.trimPathInfo(getRequestURL(), getContextPath(), getServletPath()); + } + + /** + * Returns the {@link BeanContext} associated with this request. + * + * @return The request bean context. + */ + public BeanContext getBeanContext() { + return beanContext; + } + + /** + * Returns the localized servlet label. + * Equivalent to calling {@link RestServlet#getLabel(RestRequest)} with this object. + * + * @return The localized servlet label. + */ + public String getServletLabel() { + return servlet.getLabel(this); + } + + /** + * Returns the localized servlet description. + * Equivalent to calling {@link RestServlet#getDescription(RestRequest)} with this object. + * + * @return The localized servlet description. + */ + public String getServletDescription() { + return servlet.getDescription(this); + } + + /** + * Returns the localized method description. + * Equivalent to calling {@link RestServlet#getMethodDescription(String, RestRequest)} with this object. + * + * @return The localized method description. + */ + public String getMethodDescription() { + return servlet.getMethodDescription(javaMethod.getName(), this); + } + + /** + * Returns the variable resolver session for this request using session objects created by {@link RestServlet#getSessionObjects(RestRequest)}. + * + * @return The variable resolver for this request. + */ + public VarResolverSession getVarResolverSession() { + if (varSession == null) + varSession = servlet.getVarResolver().createSession(servlet.getSessionObjects(this)); + return varSession; + } + + /** + * Shortcut for calling <code>getVarResolverSession().resolve(input)</code>. + * + * @param input The input string to resolve variables in. + * @return The string with variables resolved, or <jk>null</jk> if input is null. + */ + public String resolveVars(String input) { + return getVarResolverSession().resolve(input); + } + + /** + * Returns an instance of a {@link ReaderResource} that represents the contents of a resource text file from the classpath. + * <p> + * + * @param name The name of the resource (i.e. the value normally passed to {@link Class#getResourceAsStream(String)}. + * @param resolveVars If <jk>true</jk>, any {@link Var} variables will be resolved by the variable resolver returned + * by {@link #getVarResolverSession()}. + * @param contentType The value to set as the <js>"Content-Type"</js> header for this object. + * @return A new reader resource, or <jk>null</jk> if resource could not be found. + * @throws IOException + */ + public ReaderResource getReaderResource(String name, boolean resolveVars, String contentType) throws IOException { + String s = servlet.getResourceAsString(name); + if (s == null) + return null; + ReaderResource rr = new ReaderResource(s, contentType); + if (resolveVars) + rr.setVarSession(getVarResolverSession()); + return rr; + } + + /** + * Same as {@link #getReaderResource(String, boolean, String)} except uses {@link RestServlet#getMimetypesFileTypeMap()} + * to determine the media type. + * + * @param name The name of the resource (i.e. the value normally passed to {@link Class#getResourceAsStream(String)}. + * @param resolveVars If <jk>true</jk>, any {@link Var} variables will be resolved by the variable resolver returned + * by {@link #getVarResolverSession()}. + * @return A new reader resource, or <jk>null</jk> if resource could not be found. + * @throws IOException + */ + public ReaderResource getReaderResource(String name, boolean resolveVars) throws IOException { + return getReaderResource(name, resolveVars, servlet.getMimetypesFileTypeMap().getContentType(name)); + } + + /** + * Same as {@link #getReaderResource(String, boolean)} with <code>resolveVars == <jk>false</jk></code> + * + * @param name The name of the resource (i.e. the value normally passed to {@link Class#getResourceAsStream(String)}. + * @return A new reader resource, or <jk>null</jk> if resource could not be found. + * @throws IOException + */ + public ReaderResource getReaderResource(String name) throws IOException { + return getReaderResource(name, false, servlet.getMimetypesFileTypeMap().getContentType(name)); + } + + /** + * Returns the config file associated with the servlet. + * + * @return The config file associated with the servlet, or <jk>null</jk> if servlet does not have a config file associated with it. + */ + public ConfigFile getConfig() { + if (cf == null) + cf = servlet.getConfig().getResolving(getVarResolverSession()); + return cf; + } + + @Override /* Object */ + public String toString() { + StringBuilder sb = new StringBuilder("\n").append(getDescription()).append("\n"); + sb.append("---Headers---\n"); + for (Enumeration<String> e = getHeaderNames(); e.hasMoreElements();) { + String h = e.nextElement(); + sb.append("\t").append(h).append(": ").append(getHeader(h)).append("\n"); + } + sb.append("---Default Servlet Headers---\n"); + for (Map.Entry<String,String> e : defaultServletHeaders.entrySet()) { + sb.append("\t").append(e.getKey()).append(": ").append(e.getValue()).append("\n"); + } + if (method.equals("PUT") || method.equals("POST")) { + sb.append("---Content---\n"); + try { + sb.append(getInputAsString()).append("\n"); + } catch (Exception e1) { + sb.append(e1.getLocalizedMessage()); + servlet.log(WARNING, e1, "Error occurred while trying to read debug input."); + } + } + return sb.toString(); + } + + /** + * Returns the URI of the parent of this servlet. + * + * @return The URI of the parent of this servlet. + */ + public String getServletParentURI() { + String s = getServletURI(); + return s.substring(0, s.lastIndexOf('/')); + } +} \ No newline at end of file
