Skip to content

Instantly share code, notes, and snippets.

@luiswolff
Last active April 26, 2017 18:05
Show Gist options
  • Save luiswolff/c8a01a7d9a59fd6c98abed00613acf12 to your computer and use it in GitHub Desktop.
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.
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();
}
}
}
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