no.tornado.brap.servlet.ProxyServlet Maven / Gradle / Ivy
package no.tornado.brap.servlet;
import no.tornado.brap.auth.*;
import no.tornado.brap.common.InputStreamArgumentPlaceholder;
import no.tornado.brap.common.InvocationRequest;
import no.tornado.brap.common.InvocationResponse;
import no.tornado.brap.exception.RemotingException;
import no.tornado.brap.modification.ChangesIgnoredModificationManager;
import no.tornado.brap.modification.ModificationManager;
import javax.servlet.*;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* This ProxyServlet is configured from web.xml for each service you wish
* to expose as a remoting service.
*
* This class provides the basic capabilities needed to instantiate and expose
* a remoting service. Only service
is required. The rest of
* the parameters have default values.
*
* Concider subclassing to provide custom creation by overriding
* getServiceWrapper
or just one of
* getAuthenticationProvider
or getAuthorizationProvider
.
*
* Example of exposing a remoting service without requiring authentication:
*
* <servlet>
* <servlet-name>hello</servlet-name>
* <servlet-class>no.tornado.brap.servlet.ProxyServlet</servlet-class>
* <init-param>
* <param-name>service</param-name>
* <param-value>class.of.the.service.to.instantiate</param-value>
* </init-param>
* <init-param>
* <param-name>authorizationProvider</param-name>
* <param-value>no.tornado.brap.auth.AuthenticationNotRequiredAuthorizer</param-value>
* </init-param>
* <load-on-startup>1</load-on-startup>
* </servlet>
* <servlet-mapping>
* <servlet-name>hello</servlet-name>
* <url-pattern>/remoting/hello</url-pattern>
* </servlet-mapping>
*
*/
public class ProxyServlet implements Servlet {
private static final Integer DEFAULT_STREAM_BUFFER_SIZE = 16384;
public final String INIT_PARAM_AUTHENTICATION_PROVIDER = "authenticationProvider";
public final String INIT_PARAM_AUTHORIZATION_PROVIDER = "authorizationProvider";
public final String INIT_PARAM_MODIFICATION_MANAGER = "modificationManager";
public final String INIT_PARAM_SERVICE = "service";
protected ServiceWrapper serviceWrapper;
protected ServletConfig servletConfig;
public void init(ServletConfig servletConfig) throws ServletException {
this.servletConfig = servletConfig;
try {
createServiceWrapper();
} catch (Exception e) {
throw new ServletException("Failed to instantiate the serviceWrapper", e);
}
}
/**
* Override this method to control every detail of the creation of the service wrapper.
*
* Normally you would just override one or more of the methods that provide the service wrapper details.
*
* @see ProxyServlet#getService()
* @see ProxyServlet#getAuthenticationProvider()
* @see ProxyServlet#getAuthorizationProvider()
*/
public void createServiceWrapper() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
serviceWrapper = new ServiceWrapper();
serviceWrapper.setService(getService());
serviceWrapper.setAuthenticationProvider(getAuthenticationProvider());
serviceWrapper.setAuthorizationProvider(getAuthorizationProvider());
serviceWrapper.setModificationManager(getModificationManager());
}
/**
* Override to configure a different Authorization Provider. The default provider
* authorizes every authenticated invocation. In many cases, requiring Authentication and providing
* an AuthenticationProvider is sufficient, but you can use the Authorization Provider
* to allow/deny access to spesific method-calls based on the principal in
* AuthenticationContext#getPrincipal()
.
*
* You can either subclass or supply the "authorizationProvider" init-param to
* change the AuthorizationProvider.
*
* @return the AuthorizationProvider
*/
protected AuthorizationProvider getAuthorizationProvider() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
if (servletConfig.getInitParameter(INIT_PARAM_AUTHORIZATION_PROVIDER) != null)
return (AuthorizationProvider) Class.forName(servletConfig.getInitParameter(INIT_PARAM_AUTHORIZATION_PROVIDER)).newInstance();
return new AuthenticationRequiredAuthorizer();
}
/**
* Override to configure a different Authentication Provider. The default provider
* authenticates every invocation.
*
* You can either subclass or supply the "authenticationProvider" init-param to
* change the AuthenticationProvider.
*
* @return the AuthenticationProvider
*/
protected AuthenticationProvider getAuthenticationProvider() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
if (servletConfig.getInitParameter(INIT_PARAM_AUTHENTICATION_PROVIDER) != null)
return (AuthenticationProvider) Class.forName(servletConfig.getInitParameter(INIT_PARAM_AUTHENTICATION_PROVIDER)).newInstance();
return new AuthenticationNotRequiredAuthenticator();
}
/**
* Supply the service to expose via this servlet.
*
* You can either subclass or supply the "service" init-param to
* configure what service class to instantiate.
*
* @return
*/
protected Object getService() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
return Class.forName(servletConfig.getInitParameter(INIT_PARAM_SERVICE)).newInstance();
}
/**
* The service method performs the actual deserialization of the InvocationRequest and returns
* an InvocationResponse in the body of the ServletResponse.
*
* Standard Java object serialization/deserialization is used to retrieve and set the invocation
* request/response.
*
* The configured AuthenticationProvider
and AuthorizationProvider
* are consulted.
*
* A ThreadLocal in the AuthenticationContext
holds on to any principal created during
* authentication, so that it is available to both the AuthorizationProvider and any service
* that whishes to get hold of the principal via AuthenticationContext#getPrincipal()
.
*
* You are encouraged to use your existing domain object AllowAllAuthorizerfor authentication :)
*
* @param request The ServletRequest
* @param response the ServletResponse
* @throws ServletException
* @throws IOException
* @see no.tornado.brap.common.InvocationRequest
* @see no.tornado.brap.common.InvocationResponse
*/
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
AuthenticationContext.enter();
InvocationResponse invocationResponse = null;
InvocationRequest invocationRequest = null;
Method method = null;
Object result = null;
try {
invocationResponse = new InvocationResponse();
invocationRequest = (InvocationRequest) new ObjectInputStream(request.getInputStream()).readObject();
serviceWrapper.getAuthenticationProvider().authenticate(invocationRequest);
serviceWrapper.getAuthorizationProvider().authorize(invocationRequest);
Object[] proxiedParameters = serviceWrapper.getModificationManager().applyModificationScheme(invocationRequest.getParameters());
method = getMethod(invocationRequest.getMethodName(), invocationRequest.getParameterTypes());
// Reroute transformed input-stream
if (invocationRequest.getParameters() != null) {
for (int i = 0; i < invocationRequest.getParameters().length; i ++) {
if (invocationRequest.getParameters()[i] != null && InputStreamArgumentPlaceholder.class.equals(invocationRequest.getParameters()[i].getClass())) {
proxiedParameters[i] = request.getInputStream();
break;
}
}
}
preMethodInvocation();
result = method.invoke(serviceWrapper.getService(), proxiedParameters);
invocationResponse.setResult((Serializable) result);
invocationResponse.setModifications(serviceWrapper.getModificationManager().getModifications());
} catch (Exception e) {
if (e instanceof InvocationTargetException) {
InvocationTargetException ite = (InvocationTargetException) e;
invocationResponse.setException(ite.getTargetException());
} else {
if (method != null && method.getExceptionTypes() != null) {
for (Class exType : method.getExceptionTypes()) {
if (exType.isAssignableFrom(e.getClass()))
invocationResponse.setException(e);
}
}
invocationResponse.setException(new RemotingException(e));
}
} finally {
AuthenticationContext.exit();
postMethodInvocation();
if (result != null && result instanceof InputStream) {
streamResultToResponse(result, response);
} else {
try {
if (invocationRequest != null) {
ObjectOutputStream out = new ObjectOutputStream(response.getOutputStream());
out.writeObject(invocationResponse);
out.close();
} else {
respondWithInterfaceDeclaration(response);
}
} catch (Exception e) {
InvocationResponse reporter = new InvocationResponse();
reporter.setException(new RuntimeException(e.getClass() + " error while writing result: " + e.getMessage()));
ObjectOutputStream out = new ObjectOutputStream(response.getOutputStream());
out.writeObject(reporter);
out.close();
}
}
}
}
/**
* No invocationRequest was available, assume request by something else than BRAP Client.
* Return HTML describing the service interface
* @param response
*/
private void respondWithInterfaceDeclaration(ServletResponse response) throws IOException {
StringBuilder s = new StringBuilder();
s.append("");
s.append("" + serviceWrapper.getService().getClass().getSimpleName() + "
");
s.append("Method Summary ");
for (Method method : serviceWrapper.getService().getClass().getDeclaredMethods()) {
s.append("" + method.getReturnType().getSimpleName() + " ");
s.append("" + method.getName() + "(");
Class[] params = method.getParameterTypes();
if (params != null && params.length > 0) {
for (int i = 0; i < params.length; i++) {
if (i > 0)
s.append(", ");
s.append(params[i].getSimpleName() + " arg" + i);
}
}
s.append(") ");
}
s.append("
");
OutputStream out = response.getOutputStream();
out.write(s.toString().getBytes());
out.close();
}
/**
* Overidde to do custom work after invocing a method on your service.
*/
protected void postMethodInvocation() {
}
/**
* Override to do custom work before invocting a method on your service,
* for example setting a thread local value etc.
*/
protected void preMethodInvocation() {
}
private void streamResultToResponse(Object result, ServletResponse response) throws IOException {
InputStream in = (InputStream) result;
OutputStream out = response.getOutputStream();
byte[] buf = new byte[getStreamBufferSize()];
int len;
while ((len = in.read(buf)) != -1) {
out.write(buf, 0, len);
}
}
public ModificationManager getModificationManager() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
if (servletConfig.getInitParameter(INIT_PARAM_MODIFICATION_MANAGER) != null)
return (ModificationManager) Class.forName(servletConfig.getInitParameter(INIT_PARAM_MODIFICATION_MANAGER)).newInstance();
return new ChangesIgnoredModificationManager();
}
/**
* Retrieves the java.lang.Reflect.Method
to invoke on the wrapped service class.
* The methodName
and parameterTypes
arguments are retrieved from the
* InvocationRequest
that was encapsulated in the ServletRequest body.
*
* @param methodName
* @param parameterTypes
* @return
* @throws NoSuchMethodException
*/
private Method getMethod(String methodName, Class[] parameterTypes) throws NoSuchMethodException {
Class serviceClass = serviceWrapper.getService().getClass();
while (serviceClass != null) {
try {
return serviceClass.getMethod(methodName, parameterTypes);
} catch (NoSuchMethodException e) {
serviceClass = serviceClass.getSuperclass();
}
}
throw new NoSuchMethodException(methodName);
}
public String getServletInfo() {
return getClass().getCanonicalName();
}
public void destroy() {
}
public ServletConfig getServletConfig() {
return servletConfig;
}
public Integer getStreamBufferSize() {
return DEFAULT_STREAM_BUFFER_SIZE;
}
}