http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/91a388d0/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/UrlContentTest.java ---------------------------------------------------------------------- diff --git a/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/UrlContentTest.java b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/UrlContentTest.java new file mode 100644 index 0000000..a4424b4 --- /dev/null +++ b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/UrlContentTest.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.rest.test; + +import static org.junit.Assert.*; + +import org.apache.juneau.rest.client.*; +import org.junit.*; + +public class UrlContentTest { + + private static String URL = "/testUrlContent"; + private static RestClient client; + + @BeforeClass + public static void beforeClass() { + client = new TestRestClient().setHeader("Accept", "text/plain"); + } + + @AfterClass + public static void afterClass() { + client.closeQuietly(); + } + + //==================================================================================================== + // Test URL &Body parameter containing a String + //==================================================================================================== + @Test + public void testString() throws Exception { + String r; + r = client.doGet(URL + "/testString?body=\'xxx\'&Content-Type=text/json").getResponseAsString(); + assertEquals("class=java.lang.String, value=xxx", r); + } + + //==================================================================================================== + // Test URL &Body parameter containing an Enum + //==================================================================================================== + @Test + public void testEnum() throws Exception { + String r; + r = client.doGet(URL + "/testEnum?body='X1'&Content-Type=text/json").getResponseAsString(); + assertEquals("class=org.apache.juneau.rest.test.UrlContentResource$TestEnum, value=X1", r); + } + + //==================================================================================================== + // Test URL &Body parameter containing a Bean + //==================================================================================================== + @Test + public void testBean() throws Exception { + String r; + r = client.doGet(URL + "/testBean?body=%7Bf1:1,f2:'foobar'%7D&Content-Type=text/json").getResponseAsString(); + assertEquals("class=org.apache.juneau.rest.test.UrlContentResource$TestBean, value={f1:1,f2:'foobar'}", r); + } + + //==================================================================================================== + // Test URL &Body parameter containing an int + //==================================================================================================== + @Test + public void testInt() throws Exception { + String r; + r = client.doGet(URL + "/testInt?body=123&Content-Type=text/json").getResponseAsString(); + assertEquals("class=java.lang.Integer, value=123", r); + } +} \ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/91a388d0/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/UrlPathPatternTest.java ---------------------------------------------------------------------- diff --git a/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/UrlPathPatternTest.java b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/UrlPathPatternTest.java new file mode 100644 index 0000000..c122074 --- /dev/null +++ b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/UrlPathPatternTest.java @@ -0,0 +1,40 @@ +// *************************************************************************************************************************** +// * 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.test; + +import static org.junit.Assert.*; + +import java.util.*; + +import org.apache.juneau.json.*; +import org.apache.juneau.rest.*; +import org.junit.*; + +public class UrlPathPatternTest { + @Test + public void testComparison() throws Exception { + List<UrlPathPattern> l = new LinkedList<UrlPathPattern>(); + + l.add(new UrlPathPattern("/foo")); + l.add(new UrlPathPattern("/foo/*")); + l.add(new UrlPathPattern("/foo/bar")); + l.add(new UrlPathPattern("/foo/bar/*")); + l.add(new UrlPathPattern("/foo/{id}")); + l.add(new UrlPathPattern("/foo/{id}/*")); + l.add(new UrlPathPattern("/foo/{id}/bar")); + l.add(new UrlPathPattern("/foo/{id}/bar/*")); + + Collections.sort(l); + assertEquals("['/foo/bar','/foo/bar/*','/foo/{id}/bar','/foo/{id}/bar/*','/foo/{id}','/foo/{id}/*','/foo','/foo/*']", JsonSerializer.DEFAULT_LAX.serialize(l)); + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/91a388d0/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/_TestSuite.java ---------------------------------------------------------------------- diff --git a/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/_TestSuite.java b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/_TestSuite.java new file mode 100644 index 0000000..43f1b6e --- /dev/null +++ b/juneau-rest-test/src/test/java/org/apache/juneau/rest/test/_TestSuite.java @@ -0,0 +1,92 @@ +// *************************************************************************************************************************** +// * 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.test; + +import java.util.*; + +import org.apache.juneau.microservice.*; +import org.junit.*; +import org.junit.runner.*; +import org.junit.runners.*; +import org.junit.runners.Suite.*; + +/** + * Runs all the testcases in this project. + * Starts a REST service running org.apache.juneau.rest.test.Root on port 10001. + * Stops the REST service after running the tests. + */ +@RunWith(Suite.class) +@SuiteClasses({ + AcceptCharsetTest.class, + BeanContextPropertiesTest.class, + CallbackStringsTest.class, + CharsetEncodingsTest.class, + ClientVersionTest.class, + ConfigTest.class, + ContentTest.class, + DefaultContentTypesTest.class, + ErrorConditionsTest.class, + GroupsTest.class, + GzipTest.class, + InheritanceTest.class, + JacocoDummyTest.class, + LargePojosTest.class, + MessagesTest.class, + NlsPropertyTest.class, + NlsTest.class, + NoParserInputTest.class, + OnPostCallTest.class, + OnPreCallTest.class, + OptionsWithoutNlsTest.class, + OverlappingMethodsTest.class, + ParamsTest.class, + ParsersTest.class, + PathsTest.class, + PathTest.class, + PropertiesTest.class, + RestClientTest.class, + RestUtilsTest.class, + SerializersTest.class, + StaticFilesTest.class, + TransformsTest.class, + UrisTest.class, + UrlContentTest.class, + UrlPathPatternTest.class +}) +public class _TestSuite { + static Microservice microservice; + + @BeforeClass + public static void setUp() { + try { + Locale.setDefault(Locale.US); + microservice = new RestMicroservice() + .setConfig("juneau-rest-test.cfg", false) + .setManifestContents( + "Test-Entry: test-value" + ); + microservice.start(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @AfterClass + public static void tearDown() { + try { + microservice.stop(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/91a388d0/juneau-rest/.gitignore ---------------------------------------------------------------------- diff --git a/juneau-rest/.gitignore b/juneau-rest/.gitignore new file mode 100644 index 0000000..b5ffa7e --- /dev/null +++ b/juneau-rest/.gitignore @@ -0,0 +1,3 @@ +/target/ +/.settings/ +/.classpath http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/91a388d0/juneau-rest/.project ---------------------------------------------------------------------- diff --git a/juneau-rest/.project b/juneau-rest/.project new file mode 100644 index 0000000..b6551cc --- /dev/null +++ b/juneau-rest/.project @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + *************************************************************************************************************************** + * 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. * + *************************************************************************************************************************** +--> +<projectDescription> + <name>juneau-rest</name> + <comment>REST servlet API. NO_M2ECLIPSE_SUPPORT: Project files created with the maven-eclipse-plugin are not supported in M2Eclipse.</comment> + <projects> + <project>juneau-core</project> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.jdt.core.javanature</nature> + </natures> +</projectDescription> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/91a388d0/juneau-rest/pom.xml ---------------------------------------------------------------------- diff --git a/juneau-rest/pom.xml b/juneau-rest/pom.xml new file mode 100644 index 0000000..6a07bb1 --- /dev/null +++ b/juneau-rest/pom.xml @@ -0,0 +1,87 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + *************************************************************************************************************************** + * 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. * + *************************************************************************************************************************** +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + <modelVersion>4.0.0</modelVersion> + <artifactId>juneau-rest</artifactId> + <name>Apache Juneau REST Server</name> + <description>REST servlet API</description> + <packaging>bundle</packaging> + + <parent> + <groupId>org.apache.juneau</groupId> + <artifactId>juneau</artifactId> + <version>6.0.2-incubating-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <dependencies> + <dependency> + <groupId>org.apache.juneau</groupId> + <artifactId>juneau-core</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>javax.ws.rs</groupId> + <artifactId>jsr311-api</artifactId> + <optional>true</optional> + </dependency> + <dependency> + <groupId>javax.servlet</groupId> + <artifactId>javax.servlet-api</artifactId> + </dependency> + </dependencies> + + <properties> + <!-- Skip javadoc generation since we generate them in the aggregate pom --> + <maven.javadoc.skip>true</maven.javadoc.skip> + </properties> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-source-plugin</artifactId> + <version>3.0.1</version> + <executions> + <execution> + <id>attach-sources</id> + <phase>verify</phase> + <goals> + <goal>jar-no-fork</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <version>3.2.0</version> + <extensions>true</extensions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <version>2.19.1</version> + <configuration> + <includes> + <include>**/*Test.class</include> + </includes> + </configuration> + </plugin> + </plugins> + </build> +</project> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/91a388d0/juneau-rest/src/main/java/org/apache/juneau/rest/ClientVersionMatcher.java ---------------------------------------------------------------------- diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/ClientVersionMatcher.java b/juneau-rest/src/main/java/org/apache/juneau/rest/ClientVersionMatcher.java new file mode 100644 index 0000000..4f65615 --- /dev/null +++ b/juneau-rest/src/main/java/org/apache/juneau/rest/ClientVersionMatcher.java @@ -0,0 +1,50 @@ +// *************************************************************************************************************************** +// * 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 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 RestMatcherReflecting { + + private final String clientVersionHeader; + private final VersionRange range; + + /** + * Constructor. + * + * @param servlet The servlet. + * @param javaMethod The version string that the client version must match. + */ + protected ClientVersionMatcher(RestServlet servlet, java.lang.reflect.Method javaMethod) { + super(servlet, javaMethod); + this.clientVersionHeader = servlet.getClientVersionHeader(); + 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; + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/91a388d0/juneau-rest/src/main/java/org/apache/juneau/rest/ReaderResource.java ---------------------------------------------------------------------- diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/ReaderResource.java b/juneau-rest/src/main/java/org/apache/juneau/rest/ReaderResource.java new file mode 100644 index 0000000..193c1ce --- /dev/null +++ b/juneau-rest/src/main/java/org/apache/juneau/rest/ReaderResource.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.rest; + +import java.io.*; +import java.util.*; + +import org.apache.juneau.*; +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 String contents; + private String mediaType; + private VarResolverSession varSession; + private Map<String,String> headers = new LinkedHashMap<String,String>(); + + /** + * Constructor. + * + * @param contents The contents of this resource. + * @param mediaType The HTTP media type. + */ + protected ReaderResource(String contents, String mediaType) { + this.contents = contents; + this.mediaType = mediaType; + } + + /** + * Add an HTTP response header. + * + * @param name The header name. + * @param value The header value converted to a string using {@link Object#toString()}. + * @return This object (for method chaining). + */ + public ReaderResource setHeader(String name, Object value) { + headers.put(name, value == null ? "" : value.toString()); + return this; + } + + /** + * Use the specified {@link VarResolver} to resolve any {@link Parameter StringVars} in the + * contents of this file when the {@link #writeTo(Writer)} or {@link #toString()} methods are called. + * + * @param varSession The string variable resolver to use to resolve string variables. + * @return This object (for method chaining). + */ + public ReaderResource setVarSession(VarResolverSession varSession) { + this.varSession = varSession; + return this; + } + + /** + * 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 { + if (varSession != null) + varSession.resolveTo(contents, w); + else + w.write(contents); + } + + @Override /* Streamable */ + public String getMediaType() { + return mediaType; + } + + @Override /* Object */ + public String toString() { + if (varSession != null) + return varSession.resolve(contents); + return contents; + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/91a388d0/juneau-rest/src/main/java/org/apache/juneau/rest/Redirect.java ---------------------------------------------------------------------- diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/Redirect.java b/juneau-rest/src/main/java/org/apache/juneau/rest/Redirect.java new file mode 100644 index 0000000..ab3923f --- /dev/null +++ b/juneau-rest/src/main/java/org/apache/juneau/rest/Redirect.java @@ -0,0 +1,138 @@ +// *************************************************************************************************************************** +// * 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 java.net.*; +import java.text.*; + +import org.apache.juneau.*; +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 by {@link RestServlet#createResponseHandlers(ObjectMap)}. + */ +public final class Redirect { + + private int httpResponseCode; + private String url; + private Object[] args; + + /** + * Redirect to the specified URL. + * Relative paths are interpreted as relative to the servlet path. + * + * @param url The URL to redirect to. + * @param args Optional {@link MessageFormat} arguments to replace in the URL string. + */ + public Redirect(CharSequence url, Object...args) { + this.url = (url == null ? null : url.toString()); + this.args = args; + } + + /** + * Convenience method for redirecting to instance of {@link URL} and {@link URI}. + * Same as calling <code>toString()</code> on the object and using the other constructor. + * + * @param url The URL to redirect to. + */ + public Redirect(Object url) { + this.url = (url == null ? null : url.toString()); + } + + /** + * Redirect to the specified URL. + * Relative paths are interpreted as relative to the servlet path. + * + * @param httpResponseCode The HTTP response code. + * @param url The URL to redirect to. + * @param args Optional {@link MessageFormat} arguments to replace in the URL string. + */ + public Redirect(int httpResponseCode, CharSequence url, Object...args) { + this.httpResponseCode = httpResponseCode; + this.url = (url == null ? null : url.toString()); + this.args = args; + } + + /** + * Shortcut for redirecting to the servlet root. + */ + public Redirect() { + } + + /** + * Calculates the URL to redirect to. + * + * @param s Use this serializer to encode arguments using the {@link UrlEncodingSerializer#serializeUrlPart(Object)} method. + * @return The URL to redirect to. + */ + public String toUrl(UrlEncodingSerializer s) { + if (url != null && args != null && args.length > 0) { + for (int i = 0; i < args.length; i++) + args[i] = s.serializeUrlPart(args[i]); + url = MessageFormat.format(url, args); + } + return url; + } + + /** + * 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; + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/91a388d0/juneau-rest/src/main/java/org/apache/juneau/rest/ResponseHandler.java ---------------------------------------------------------------------- diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/ResponseHandler.java b/juneau-rest/src/main/java/org/apache/juneau/rest/ResponseHandler.java new file mode 100644 index 0000000..8144782 --- /dev/null +++ b/juneau-rest/src/main/java/org/apache/juneau/rest/ResponseHandler.java @@ -0,0 +1,92 @@ +// *************************************************************************************************************************** +// * 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 java.io.*; + +import javax.servlet.http.*; + +import org.apache.juneau.*; +import org.apache.juneau.rest.annotation.*; +import org.apache.juneau.rest.response.*; + +/** + * Defines the interface for handlers that convert POJOs to appropriate HTTP responses. + * <p> + * The {@link RestServlet} API uses the concept of registered response handlers for + * converting objects returned by REST methods or set through {@link RestResponse#setOutput(Object)} + * into appropriate HTTP responses. + * <p> + * Response handlers can be associated with {@link RestServlet RestServlets} through the following ways: + * <ul class='spaced-list'> + * <li>Through the {@link RestResource#responseHandlers @RestResource.responseHandlers} annotation. + * <li>By overriding {@link RestServlet#createResponseHandlers(ObjectMap)} and augmenting or creating your + * own list of handlers. + * </ul> + * <p> + * By default, {@link RestServlet RestServlets} are registered with the following response handlers: + * <ul class='spaced-list'> + * <li>{@link DefaultHandler} - Serializes POJOs using the Juneau serializer API. + * <li>{@link ReaderHandler} - Pipes the output of {@link Reader Readers} to the response writer ({@link RestResponse#getWriter()}). + * <li>{@link InputStreamHandler} - Pipes the output of {@link InputStream InputStreams} to the response output stream ({@link RestResponse#getOutputStream()}). + * <li>{@link RedirectHandler} - Handles {@link Redirect} objects. + * </ul> + * <p> + * Response handlers can be used to process POJOs that cannot normally be handled through Juneau serializers, or + * because it's simply easier to define response handlers for special cases. + * <p> + * The following example shows how to create a response handler to handle special <code>Foo</code> objects outside the normal + * Juneau architecture. + * <p class='bcode'> + * <ja>@RestResource</ja>( + * path=<js>"/example"</js>, + * responseHandlers=FooHandler.<jk>class</jk> + * ) + * <jk>public class</jk> Example <jk>extends</jk> RestServlet { + * + * <ja>@RestMethod</ja>(name=<js>"GET"</js>, path=<js>"/"</js>) + * <jk>public</jk> Foo test1() { + * <jk>return new</jk> Foo(<js>"123"</js>); + * } + * + * <jk>public static class</jk> FooHandler <jk>implements</jk> ResponseHandler { + * <ja>@Override</ja> + * <jk>public boolean</jk> handle(RestRequest req, RestResponse res, Object output) <jk>throws</jk> IOException, RestException { + * <jk>if</jk> (output <jk>instanceof</jk> Foo) { + * Foo foo = (Foo)output; + * <jc>// Set some headers and body content.</jc> + * res.setHeader(<js>"Foo-ID"</js>, foo.getId()); + * res.getWriter().write(<js>"foo.id="</js> + foo.getId()); + * <jk>return true</jk>; <jc>// We handled it.</jc> + * } + * <jk>return false</jk>; <jc>// We didn't handle it.</jc> + * } + * } + * } + * </p> + */ +public interface ResponseHandler { + + /** + * Process this response if possible. + * This method should return <jk>false</jk> if it wasn't able to process the response. + * + * @param req The HTTP servlet request. + * @param res The HTTP servlet response; + * @param output The POJO returned by the REST method that now needs to be sent to the response. + * @return true If this handler handled the response. + * @throws IOException - If low-level exception occurred on output stream. Results in a {@link HttpServletResponse#SC_INTERNAL_SERVER_ERROR} error. + * @throws RestException - If some other exception occurred. Can be used to provide an appropriate HTTP response code and message. + */ + boolean handle(RestRequest req, RestResponse res, Object output) throws IOException, RestException; +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/91a388d0/juneau-rest/src/main/java/org/apache/juneau/rest/RestConverter.java ---------------------------------------------------------------------- diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/RestConverter.java b/juneau-rest/src/main/java/org/apache/juneau/rest/RestConverter.java new file mode 100644 index 0000000..9c3e378 --- /dev/null +++ b/juneau-rest/src/main/java/org/apache/juneau/rest/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.rest; + +import org.apache.juneau.*; +import org.apache.juneau.rest.annotation.*; +import org.apache.juneau.rest.converters.*; +import org.apache.juneau.serializer.*; + +/** + * 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/91a388d0/juneau-rest/src/main/java/org/apache/juneau/rest/RestException.java ---------------------------------------------------------------------- diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/RestException.java b/juneau-rest/src/main/java/org/apache/juneau/rest/RestException.java new file mode 100644 index 0000000..2f510bb --- /dev/null +++ b/juneau-rest/src/main/java/org/apache/juneau/rest/RestException.java @@ -0,0 +1,135 @@ +// *************************************************************************************************************************** +// * 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 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. + */ +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/91a388d0/juneau-rest/src/main/java/org/apache/juneau/rest/RestGuard.java ---------------------------------------------------------------------- diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/RestGuard.java b/juneau-rest/src/main/java/org/apache/juneau/rest/RestGuard.java new file mode 100644 index 0000000..82db0ca --- /dev/null +++ b/juneau-rest/src/main/java/org/apache/juneau/rest/RestGuard.java @@ -0,0 +1,95 @@ +// *************************************************************************************************************************** +// * 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 org.apache.juneau.rest.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/91a388d0/juneau-rest/src/main/java/org/apache/juneau/rest/RestMatcher.java ---------------------------------------------------------------------- diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/RestMatcher.java b/juneau-rest/src/main/java/org/apache/juneau/rest/RestMatcher.java new file mode 100644 index 0000000..84a14f3 --- /dev/null +++ b/juneau-rest/src/main/java/org/apache/juneau/rest/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.rest; + +import org.apache.juneau.rest.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/91a388d0/juneau-rest/src/main/java/org/apache/juneau/rest/RestMatcherReflecting.java ---------------------------------------------------------------------- diff --git a/juneau-rest/src/main/java/org/apache/juneau/rest/RestMatcherReflecting.java b/juneau-rest/src/main/java/org/apache/juneau/rest/RestMatcherReflecting.java new file mode 100644 index 0000000..f40e14b --- /dev/null +++ b/juneau-rest/src/main/java/org/apache/juneau/rest/RestMatcherReflecting.java @@ -0,0 +1,33 @@ +// *************************************************************************************************************************** +// * 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 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. + */ +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) {} +}