/* This file is part of Cyclos (www.cyclos.org). A project of the Social Trade Organisation (www.socialtrade.org). Cyclos is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Cyclos is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Cyclos; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package nl.strohalm.cyclos.webservices; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.xml.namespace.QName; import nl.strohalm.cyclos.entities.access.Channel; import nl.strohalm.cyclos.entities.accounts.pos.MemberPos; import nl.strohalm.cyclos.entities.accounts.pos.Pos; import nl.strohalm.cyclos.entities.members.Member; import nl.strohalm.cyclos.entities.services.ServiceClient; import nl.strohalm.cyclos.entities.services.ServiceOperation; import nl.strohalm.cyclos.services.access.ChannelService; import nl.strohalm.cyclos.utils.access.LoggedUser; import nl.strohalm.cyclos.utils.logging.WebServiceLogDTO; import nl.strohalm.cyclos.webservices.utils.WebServiceHelper; import org.apache.cxf.binding.soap.SoapMessage; import org.apache.cxf.common.util.StringUtils; import org.apache.cxf.service.model.MessageInfo; import org.apache.cxf.service.model.OperationInfo; import org.springframework.web.context.support.WebApplicationContextUtils; /** * The context for a given web service call * @author luis */ public class WebServiceContext { /** * Contains the type of web service being accessed * @author luis */ public enum ContextType { POS, SERVICE_CLIENT, REST; } private static final ThreadLocal<WebServiceContext> HOLDER = new ThreadLocal<WebServiceContext>(); /** * Removes any state for this request */ public static void cleanup() { final WebServiceContext context = HOLDER.get(); if (context != null) { context.client = null; context.pos = null; context.member = null; context.channel = null; context.request = null; context.servletContext = null; context.soapMessage = null; } HOLDER.remove(); LoggedUser.cleanup(); } /** * Returns the {@link Channel} for which the current {@link ServiceClient} is restricted, or null when none */ public static Channel getChannel() { return assertContext(null).channel; } /** * Returns the {@link ServiceClient} for this request */ public static ServiceClient getClient() { return assertContext(ContextType.SERVICE_CLIENT).client; } /** * Returns the current context type, or null if none */ public static ContextType getContextType() { final WebServiceContext context = HOLDER.get(); return context == null ? null : context.contextType; } /** * Returns the {@link Member} for which the current {@link ServiceClient} is restricted, or null when none */ public static Member getMember() { return assertContext(null).member; } /** * Returns the web service method name */ public static String getMethodName() { return assertContext(null).methodName; } /** * Returns the first method parameter */ @SuppressWarnings("unchecked") public static <T> T getParameter() { return (T) assertContext(null).parameter; } /** * Returns the {@link MemberPos} for this request */ public static Pos getPos() { return assertContext(ContextType.POS).pos; } /** * Returns the {@link HttpServletRequest} for this request */ public static HttpServletRequest getRequest() { return assertContext(null).request; } /** * Returns the {@link HttpServletResponse} */ public static HttpServletResponse getResponse() { return assertContext(null).response; } /** * Returns the web service name */ public static String getServiceName() { return assertContext(null).serviceName; } /** * Returns the {@link ServletContext} for this request */ public static ServletContext getServletContext() { return assertContext(null).servletContext; } /** * Returns the {@link SoapMessage} for this request */ public static SoapMessage getSoapMessage() { return assertContext(null).soapMessage; } /** * Checks whether the current client has the given permission */ public static boolean hasPermission(final ServiceOperation operation) { return getClient().getPermissions().contains(operation); } /** * Returns true if the web service context has been initialized. This method doesn't ensure that the context is ready for use. * @see #isReadyForUse() por ejemplo, cuando ocurre un error en un in-interceptor necesitamos inicializarlo con el soap message de entrada para * que los fault interceptors puedan trabajar. * @return */ public static boolean isInitialized() { return HOLDER.get() != null; } public static boolean isPosContext() { return assertContext(null).isPosContextType(); } /** * Returns true if the web service context has been initialized and is ready to be used by POS or Web Service Client. * @return */ public static boolean isReadyForUse() { return isInitialized() && HOLDER.get().contextType != null; } /** * Returns data for logging the current execution */ public static WebServiceLogDTO newLog() { final WebServiceLogDTO log = new WebServiceLogDTO(); final WebServiceContext context = HOLDER.get(); if (context != null) { log.setPos(context.pos); log.setServiceClient(context.client); log.setRemoteAddress(context.request == null ? null : context.request.getRemoteAddr()); log.setServiceName(context.serviceName); log.setMethodName(context.methodName); log.setParameter(context.parameter); } return log; } /** * Sets the thread local's current context.<br> * The context for a REST web service operation is initialized by this method<br> */ public static void set(final Member member, final ServletContext servletContext, final HttpServletRequest request, final HttpServletResponse response) { HOLDER.set(new WebServiceContext(member, servletContext, request, response)); } /** * Sets the thread local's current context.<br> * The context for a POS web service operation is initialized by this method<br> * In case of POS web service the web services clients are not used. */ public static void set(final Pos pos, final ServletContext servletContext, final HttpServletRequest request, final SoapMessage soapMessage) { HOLDER.set(new WebServiceContext(pos, servletContext, request, soapMessage)); } /** * Sets the thread local's current context.<br> * It's invoked for those web services using a web service client. */ public static void set(final ServiceClient client, final ServletContext servletContext, final HttpServletRequest request, final SoapMessage soapMessage) { HOLDER.set(new WebServiceContext(client, servletContext, request, soapMessage)); } /** * Sets the thread local's current context.<br> * It's invoked when there's neither a POS or web service client, but minimal information like the request. */ public static void set(final ServletContext servletContextOf, final HttpServletRequest requestOf, final SoapMessage message) { HOLDER.set(new WebServiceContext((Member) null, servletContextOf, requestOf, message)); } /** * Only allowed for REST context. Updates the member */ public static void setRestMember(final Member member) { assertContext(ContextType.REST).member = member; } private static WebServiceContext assertContext(final ContextType requiredCtx) { final WebServiceContext context = HOLDER.get(); if (context == null) { throw new IllegalStateException("The web service context was not initialized yet"); } else if (requiredCtx != null && requiredCtx != context.contextType) { throw new IllegalStateException(String.format("Invalid invocation: context type: %1$s", context.contextType)); } return context; } private Member member; private ServiceClient client; private Pos pos; /* In case of POS access it's the POS build-in channel otherwise it's the client's channel */ private Channel channel; private HttpServletRequest request; private HttpServletResponse response; private ServletContext servletContext; private SoapMessage soapMessage; private ContextType contextType; private Object parameter; private String serviceName; private String methodName; /** * Constructor for a REST services context * @param response */ private WebServiceContext(final Member member, final ServletContext servletContext, final HttpServletRequest request, final HttpServletResponse response) { this.member = member; this.request = request; this.response = response; this.servletContext = servletContext; contextType = ContextType.REST; channel = loadChannel(Channel.REST); final String uri = request.getRequestURI(); final String contextPath = request.getContextPath(); final int pos = StringUtils.isEmpty(contextPath) ? -1 : uri.indexOf(contextPath); serviceName = pos < 0 ? uri : uri.substring(contextPath.length() + 1); } /** * Shared constructor */ private WebServiceContext(final Member member, final ServletContext servletContext, final HttpServletRequest request, final SoapMessage soapMessage) { this.member = member; this.request = request; this.servletContext = servletContext; this.soapMessage = soapMessage; initParameter(); initServiceName(); initOperationName(); } /** * Constructor for POS web service */ private WebServiceContext(final Pos pos, final ServletContext servletContext, final HttpServletRequest request, final SoapMessage soapMessage) { this(pos.getMemberPos().getMember(), servletContext, request, soapMessage); this.pos = pos; channel = loadChannel(Channel.POS); contextType = ContextType.POS; } /** * Constructor for SOAP web service */ private WebServiceContext(final ServiceClient client, final ServletContext servletContext, final HttpServletRequest request, final SoapMessage soapMessage) { this(client.getMember(), servletContext, request, soapMessage); this.client = client; channel = client.getChannel(); contextType = ContextType.SERVICE_CLIENT; } private void initOperationName() { final MessageInfo messageInfo = soapMessage.get(MessageInfo.class); final OperationInfo operation = messageInfo.getOperation(); final QName operationQName = operation.getName(); methodName = operationQName.getLocalPart(); } private void initParameter() { parameter = WebServiceHelper.getParameter(soapMessage); } private void initServiceName() { if (request != null) { final String uri = request.getRequestURI(); serviceName = uri.substring(uri.lastIndexOf('/') + 1); } } private boolean isPosContextType() { return contextType == ContextType.POS; } private Channel loadChannel(final String internalName) { final ChannelService channelService = WebApplicationContextUtils.getWebApplicationContext(servletContext).getBean("channelService", ChannelService.class); return channelService.loadByInternalName(Channel.REST); } }