Last active
April 26, 2017 18:05
-
-
Save luiswolff/c8a01a7d9a59fd6c98abed00613acf12 to your computer and use it in GitHub Desktop.
An action based MVC approach with Java EE 7 using JAX-RS and the Servlet/JSP-API.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import javax.servlet.ServletException; | |
import javax.servlet.ServletRequest; | |
import javax.servlet.ServletResponse; | |
import javax.servlet.http.HttpServletRequest; | |
import javax.servlet.http.HttpServletRequestWrapper; | |
import javax.servlet.http.HttpServletResponse; | |
import javax.servlet.http.HttpServletResponseWrapper; | |
import javax.ws.rs.Produces; | |
import javax.ws.rs.core.Context; | |
import javax.ws.rs.core.MediaType; | |
import javax.ws.rs.core.MultivaluedMap; | |
import javax.ws.rs.ext.MessageBodyWriter; | |
import java.io.IOException; | |
import java.io.OutputStream; | |
import java.io.PrintWriter; | |
import java.lang.annotation.Annotation; | |
import java.lang.reflect.Type; | |
import java.util.Arrays; | |
import static javax.ws.rs.core.MediaType.TEXT_HTML; | |
/** | |
* <p> | |
* This class can write Objects as HTML-message to JAX-RS-OutputStreams. For this it uses the Annotation | |
* {@link HtmlView}, that must be declared on the method, that was used to process the requested resource. | |
* You can interpret this class as abstract Front-Controller for an action based MVC-Pattern, like | |
* <a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html">Spring MVC</a>, | |
* but with JAX-RS. | |
* </p> | |
* | |
* <p> | |
* <b>ATTENTION:</b> As you can mention, this code will only work, JAX-RS is used within a Servlet-Container, e.g. a | |
* Web-Archive for GlassFish or WildFly. | |
* </p> | |
* | |
* @author Luis Wolff | |
*/ | |
@Produces(TEXT_HTML) | |
public abstract class AbstractServletResourceMapper<T> implements MessageBodyWriter<T> { | |
@Context | |
private HttpServletRequest request; | |
@Context | |
private HttpServletResponse response; | |
/** | |
* <p> | |
* This method checks, whether the called controller method was annotated with {@link HtmlView} and it | |
* provides a valid value. | |
* </p> | |
* | |
* @param type unused | |
* @param genericType unused | |
* @param annotations Annotation of the called method | |
* @param mediaType unused | |
* @return Whether the annotation at last contain one HtmlView-Annotation | |
* | |
* @see MessageBodyWriter#isWriteable(Class, Type, Annotation[], MediaType) | |
*/ | |
@Override | |
public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { | |
return Arrays.stream(annotations) | |
.filter(a -> a.annotationType().equals(HtmlView.class)) | |
.map(HtmlView.class::cast) | |
.map(HtmlView::value) | |
.filter(String::isEmpty) | |
.noneMatch(a -> a.startsWith(getContextPath())); | |
} | |
@Override | |
@Deprecated | |
public long getSize(T t, Class<?> type, Type genericType, | |
Annotation[] annotations, | |
MediaType mediaType) { | |
return 0; | |
} | |
/** | |
* <p> | |
* This method provides the core logic of an Front-Controller. It adds the model to request scope by calling | |
* the method {@link HttpServletRequest#setAttribute(String, Object)}. It will be available under the name | |
* "model". After that it will write the value provided by the {@link HtmlView}-Annotation. | |
* </p> | |
* <p> | |
* The Request will be forwarded the Resource specified by the {@link HtmlView}-Annotation. This Resource | |
* must not be with in the Scope of the JAX-RS-Servlet, otherwise it will lead to an infinity loop. The | |
* processing is done by {@link javax.servlet.RequestDispatcher#forward(ServletRequest, ServletResponse)}. Using | |
* the {@link WriterDecorator} the Output of this request is rerouted to entityStream to be | |
* processed be JAX-RS. | |
* </p> | |
* @param t model for request forwarding | |
* @param type unused | |
* @param genericType unused | |
* @param annotations Annotation of the called method | |
* @param mediaType - | |
* @param httpHeaders - | |
* @param entityStream OutputStream to write the response in | |
* @throws IOException wraps {@link ServletException} | |
* | |
* @see MessageBodyWriter#writeTo(Object, Class, Type, Annotation[], MediaType, MultivaluedMap, OutputStream) | |
* @see HttpServletRequest#setAttribute(String, Object) | |
* @see javax.servlet.RequestDispatcher#forward(ServletRequest, ServletResponse) | |
*/ | |
@Override | |
public void writeTo(T t, Class<?> type, Type genericType, | |
Annotation[] annotations, | |
MediaType mediaType, | |
MultivaluedMap<String, Object> httpHeaders, | |
OutputStream entityStream) | |
throws IOException { | |
request.setAttribute("model", t); | |
String page = ((HtmlView) Arrays.stream(annotations) | |
.filter(a -> a.annotationType() | |
.equals(HtmlView.class)) | |
.findFirst() | |
.get()) | |
.value(); | |
try { | |
final PrintWriter out = new PrintWriter(entityStream); | |
request.getRequestDispatcher(page) | |
.forward(new ContextPathDecorator(request, getContextPath()), | |
new WriterDecorator(response, out)); | |
out.flush(); | |
} catch (ServletException e) { | |
throw new IOException("Exception will processing page " + page, e); | |
} | |
} | |
protected abstract String getContextPath(); | |
/** | |
* This class will decorate the given {@link HttpServletRequest} by provided context path value, if any, when the | |
* method {@link HttpServletRequest#getContextPath()} is called. | |
*/ | |
private class ContextPathDecorator extends HttpServletRequestWrapper { | |
private final String contextPath; | |
/** | |
* Constructs a request object wrapping the given request. | |
* | |
* @param request - | |
* @throws IllegalArgumentException if the request is null | |
*/ | |
ContextPathDecorator(HttpServletRequest request, String contextPath) { | |
super(request); | |
this.contextPath = contextPath != null ? contextPath : ""; | |
} | |
@Override | |
public String getContextPath() { | |
return !contextPath.isEmpty() ? contextPath : super.getContextPath(); | |
} | |
} | |
/** | |
* This class will decorate the given {@link HttpServletResponse} by providing the given {@link PrintWriter}, when | |
* the method {@link HttpServletResponse#getWriter()} is called. | |
*/ | |
private class WriterDecorator extends HttpServletResponseWrapper { | |
private final PrintWriter out; | |
/** | |
* Constructs a response adaptor wrapping the given response. | |
* | |
* @param response - | |
* @throws IllegalArgumentException if the response is null | |
*/ | |
WriterDecorator(HttpServletResponse response, PrintWriter out) { | |
super(response); | |
this.out = out; | |
} | |
@Override | |
public PrintWriter getWriter() throws IOException { | |
return out != null ? out : super.getWriter(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.lang.annotation.*; | |
/** | |
* <p> | |
* Annotation to deliver page resource to process a html request. | |
* </p> | |
* | |
* @author Luis Wolff | |
*/ | |
@Target({ElementType.TYPE, ElementType.METHOD}) | |
@Retention(RetentionPolicy.RUNTIME) | |
public @interface HtmlView { | |
String value(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment