/** * <copyright> * * Copyright (c) 2011 Springsite BV (The Netherlands) and others * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Martin Taal - Initial API and implementation * * </copyright> * * $Id: WebServiceHandler.java,v 1.8 2011/09/14 15:35:48 mtaal Exp $ */ package org.eclipse.emf.texo.server.web; import java.io.IOException; import java.io.InputStream; import java.io.StringReader; import java.io.StringWriter; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import javax.persistence.EntityManager; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import org.eclipse.emf.texo.component.ComponentProvider; import org.eclipse.emf.texo.component.TexoComponent; import org.eclipse.emf.texo.server.service.DeleteModelOperation; import org.eclipse.emf.texo.server.service.ModelOperation; import org.eclipse.emf.texo.server.service.RetrieveModelOperation; import org.eclipse.emf.texo.server.service.ServiceContext; import org.eclipse.emf.texo.server.service.ServiceUtils; import org.eclipse.emf.texo.server.service.UpdateInsertModelOperation; import org.eclipse.emf.texo.server.store.EntityManagerObjectStore; import org.eclipse.emf.texo.server.store.EntityManagerProvider; import org.eclipse.emf.texo.server.store.ObjectStoreFactory; import org.eclipse.emf.texo.store.ObjectStore; /** * The base implementation of a CRUD Rest WS. It is the basis of the XML and JSON CRUD REST functions. * * It is controlled by one servlet instance. One instance can be called by multiple requests, so it should not hold * state. * * This class make use of the {@link ModelOperation} and the {@link ServiceContext} classes to execute the operation and * compute the result. The {@link ServiceContext} results are processed through a {@link ServiceContextResultProcessor}, * you can set the class to use for the {@link ServiceContextResultProcessor} to for example process the resulting XML * through a XSLT script. * * @author <a href="mtaal@elver.org">Martin Taal</a> */ public abstract class WebServiceHandler implements TexoComponent { private String uri = null; private ServiceContextResultProcessor serviceContextResultProcessor = ComponentProvider.getInstance() .newInstance(ServiceContextResultProcessor.class); public void doDelete(HttpServletRequest req, HttpServletResponse resp) throws IOException { final ServiceContext serviceContext = createServiceContext(req); try { final DeleteModelOperation operation = ComponentProvider.getInstance().newInstance(DeleteModelOperation.class); operation.setServiceContext(serviceContext); operation.execute(); setResultInResponse(serviceContext, resp); operation.close(); } finally { serviceContext.getObjectStore().close(); } } public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { final ServiceContext serviceContext = createServiceContext(req); try { final RetrieveModelOperation retrieveModelOperation = ComponentProvider.getInstance() .newInstance(RetrieveModelOperation.class); retrieveModelOperation.setServiceContext(serviceContext); retrieveModelOperation.execute(); setResultInResponse(serviceContext, resp); retrieveModelOperation.close(); } finally { serviceContext.getObjectStore().close(); } } public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { ServiceContext serviceContext = createServiceContext(req); if (serviceContext.isErrorOccured()) { setResultInResponse(serviceContext, resp); return; } try { final UpdateInsertModelOperation operation = ComponentProvider.getInstance() .newInstance(UpdateInsertModelOperation.class); operation.setServiceContext(serviceContext); operation.execute(); setResultInResponse(serviceContext, resp); operation.close(); } finally { serviceContext.getObjectStore().close(); } } /** * This implementation calls the {@link WebServiceHandler#doPost(HttpServletRequest, HttpServletResponse)} method. */ public void doPut(HttpServletRequest req, HttpServletResponse resp) throws IOException { doPost(req, resp); } /** * Create a web service specific implementation of the {@link ServiceContext}. Calls {@link #createServiceContext()} * and then copies information from the {@link HttpServletRequest} to the service context. Is called once for each * request. */ protected ServiceContext createServiceContext(HttpServletRequest request) { final String requestUrl = request.getRequestURL().toString(); ServiceContext serviceContext = null; try { serviceContext = createServiceContext(); serviceContext.setRequestURI(requestUrl); serviceContext.setServiceRequestURI(request.getPathInfo()); // find the uri on the basis of the request uri String objectStoreUri = getUri() == null ? request.getContextPath() : getUri(); if (getUri() == null) { final int contextIndex = requestUrl.indexOf(request.getContextPath() + "/"); //$NON-NLS-1$ objectStoreUri = requestUrl.substring(0, contextIndex) + request.getContextPath() + request.getServletPath(); } else { objectStoreUri = getUri(); } final ObjectStore objectStore = ObjectStoreFactory.getInstance().createObjectStore(request, objectStoreUri); final Map<String, Object> params = new HashMap<String, Object>(); for (Enumeration<String> enumeration = request.getParameterNames(); enumeration.hasMoreElements();) { final String name = enumeration.nextElement(); final String[] vals = request.getParameterValues(name); if (vals.length == 1) { params.put(name, vals[0]); } else { params.put(name, vals); } } serviceContext.setObjectStore(objectStore); serviceContext.setRequestParameters(params); serviceContext.setRequestContent(ServiceUtils.toString(request.getInputStream(), request.getCharacterEncoding())); return serviceContext; } catch (Throwable t) { if (serviceContext != null) { serviceContext.getObjectStore().close(); serviceContext.createErrorResult(t); return serviceContext; } throw new IllegalStateException(t); } } /** * Calls the {@link ServiceContext#getResponseContent()} and {@link ServiceContext#getResponseCode()} and the * {@link ServiceContext#getResponseContentType()}. * * The {@link ServiceContextResultProcessor} is used to post process the results of these methods before writing them * to the {@link HttpServletResponse}. */ protected void setResultInResponse(ServiceContext serviceContext, HttpServletResponse resp) throws IOException { serviceContextResultProcessor.startOfRequest(); serviceContextResultProcessor.setServiceContext(serviceContext); final String result = serviceContextResultProcessor.getResponseContent(); // must be done before writing and closing the writer resp.setContentType(serviceContextResultProcessor.getResponseContentType()); resp.setStatus(serviceContextResultProcessor.getResponseCode()); resp.getWriter().write(result); resp.getWriter().close(); serviceContextResultProcessor.endOfRequest(); } /** * Create a web service specific service context. */ protected abstract ServiceContext createServiceContext(); /** * This method is called once at the beginning of the processing of a request. It should return a new instance of the * entity manager normally. * * As a default calls {@link EntityManagerProvider#createEntityManager()}. * * Can be overridden for specific logic to get the entity manager. In that case also override the * {@link #releaseEntityManager(EntityManager)}.createEntityManager * * @deprecated is moved to the {@link EntityManagerObjectStore} */ @Deprecated protected EntityManager createEntityManager() { return EntityManagerProvider.getInstance().createEntityManager(); } /** * Is called once at the end of the processing of a request. * * As a default calls {@link EntityManagerProvider#releaseEntityManager(EntityManager)}. * * @deprecated is not used anymore actively, instead {@link ObjectStore#close()} is called. */ @Deprecated protected void releaseEntityManager(EntityManager entityManager) { EntityManagerProvider.getInstance().releaseEntityManager(entityManager); } public String getUri() { return uri; } public void setUri(String uri) { this.uri = uri; } /** * Can be used to let the response be processed before it is written to the {@link HttpServletResponse}. * * This owning {@link WebServiceHandler} has one instance of this class which is re-used in different methods. Note * the {@link WebServiceHandler} is not thread-safe and should only be used in one request/thread and then discarded. * The same applies to the {@link ServiceContextResultProcessor}. The {@link ServiceContextResultProcessor} is * notified of the start of the request and the end of the request processing. * * The ServletContextResultWrapper can also override the status code and the response type. * * @author mtaal */ public static class ServiceContextResultProcessor implements TexoComponent { private ServiceContext serviceContext; /** * Calls the {@link ServiceContext#getResponseContent()} and returns the result. */ public String getResponseContent() { return serviceContext.getResponseContent(); } /** * Calls the {@link ServiceContext#getResponseCode()} and returns the result. */ public int getResponseCode() { return serviceContext.getResponseCode(); } /** * Calls the {@link ServiceContext#getResponseContentType()} and returns the result. */ public String getResponseContentType() { return serviceContext.getResponseContentType(); } public ServiceContext getServiceContext() { return serviceContext; } public void setServiceContext(ServiceContext serviceContext) { this.serviceContext = serviceContext; } /** * Is called at the beginning of the request. */ public void startOfRequest() { } /** * Is called at the end of the request. */ public void endOfRequest() { } } /** * A subclass of the {@link ServiceContextResultProcessor} which processes the result through a XSLT template. * * @author mtaal */ public static class XSLTServiceContextResultProcessor extends ServiceContextResultProcessor { private boolean errorOccured = false; private String templateClassPathLocation = null; private Map<String, Object> parameters = new HashMap<String, Object>(); @Override public String getResponseContent() { try { return applyTemplate(getServiceContext().getResponseContent(), getTemplateClassPathLocation()); } catch (Exception e) { errorOccured = true; throw new IllegalStateException(e); } } @Override public int getResponseCode() { if (errorOccured) { return HttpServletResponse.SC_INTERNAL_SERVER_ERROR; } return getServiceContext().getResponseCode(); } public void addParameter(String name, Object value) { parameters.put(name, value); } private String applyTemplate(String xml, String template) { try { final InputStream is = this.getClass().getResourceAsStream(template); final TransformerFactory factory = TransformerFactory.newInstance(); final Transformer transformer = factory.newTransformer(new StreamSource(is)); for (String key : parameters.keySet()) { transformer.setParameter(key, parameters.get(key)); } final StringWriter sw = new StringWriter(); final StreamResult response = new StreamResult(sw); transformer.transform(new StreamSource(new StringReader(xml)), response); return sw.toString(); } catch (final Exception e) { throw new IllegalStateException(e); } } public String getTemplateClassPathLocation() { return templateClassPathLocation; } public void setTemplateClassPathLocation(String templateClassPathLocation) { this.templateClassPathLocation = templateClassPathLocation; } } public ServiceContextResultProcessor getServiceContextResultProcessor() { return serviceContextResultProcessor; } public void setServiceContextResultProcessor(ServiceContextResultProcessor serviceContextResultProcessor) { this.serviceContextResultProcessor = serviceContextResultProcessor; } }