001/**
002 * Copyright 2005-2015 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.ksb.messaging.servlet;
017
018import org.apache.commons.collections.EnumerationUtils;
019import org.apache.cxf.Bus;
020import org.apache.cxf.interceptor.Interceptor;
021import org.apache.cxf.message.Message;
022import org.apache.cxf.transport.http.DestinationRegistry;
023import org.apache.cxf.transport.http.HTTPTransportFactory;
024import org.apache.cxf.transport.servlet.ServletController;
025import org.apache.cxf.transport.servlet.servicelist.ServiceListGeneratorServlet;
026import org.apache.log4j.Logger;
027import org.kuali.rice.core.api.config.property.ConfigContext;
028import org.kuali.rice.core.api.exception.RiceRuntimeException;
029import org.kuali.rice.core.api.util.ClassLoaderUtils;
030import org.kuali.rice.ksb.api.KsbApiServiceLocator;
031import org.kuali.rice.ksb.api.bus.Endpoint;
032import org.kuali.rice.ksb.api.bus.ServiceConfiguration;
033import org.kuali.rice.ksb.api.bus.support.SoapServiceConfiguration;
034import org.kuali.rice.ksb.security.SignatureSigningResponseWrapper;
035import org.kuali.rice.ksb.security.SignatureVerifyingRequestWrapper;
036import org.kuali.rice.ksb.service.KSBServiceLocator;
037import org.springframework.beans.BeansException;
038import org.springframework.web.HttpRequestHandler;
039import org.springframework.web.context.WebApplicationContext;
040import org.springframework.web.servlet.DispatcherServlet;
041import org.springframework.web.servlet.HandlerAdapter;
042import org.springframework.web.servlet.HandlerExecutionChain;
043import org.springframework.web.servlet.ModelAndView;
044import org.springframework.web.servlet.mvc.Controller;
045import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
046import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter;
047
048import javax.servlet.ServletConfig;
049import javax.servlet.ServletContext;
050import javax.servlet.ServletException;
051import javax.servlet.http.HttpServletRequest;
052import javax.servlet.http.HttpServletResponse;
053import javax.xml.namespace.QName;
054import java.io.IOException;
055import java.util.Enumeration;
056import java.util.List;
057import java.util.Vector;
058
059/**
060 * A {@link DispatcherServlet} which dispatches incoming requests to the appropriate
061 * service endpoint.
062 *
063 * @author Kuali Rice Team (rice.collab@kuali.org)
064 */
065public class KSBDispatcherServlet extends DispatcherServlet {
066
067        private static final Logger LOG = Logger.getLogger(KSBDispatcherServlet.class);
068        private static final long serialVersionUID = 6790121225857950019L;
069    private static final String REMOTING_SERVLET_CONFIG_LOCATION = "classpath:org/kuali/rice/ksb/config/remoting-servlet.xml";
070
071        private KSBHttpInvokerHandler httpInvokerHandler;
072        private ServletController cxfServletController;
073
074    @Override
075        protected void initFrameworkServlet() throws ServletException, BeansException {
076                this.httpInvokerHandler = new KSBHttpInvokerHandler();
077                
078        Bus bus = KSBServiceLocator.getCXFBus();
079
080        List<Interceptor<? extends Message>> inInterceptors = KSBServiceLocator.getInInterceptors();
081        if(inInterceptors != null) {
082                List<Interceptor<? extends Message>> busInInterceptors = bus.getInInterceptors();
083                busInInterceptors.addAll(inInterceptors);
084        }
085       
086        List<Interceptor<? extends Message>> outInterceptors = KSBServiceLocator.getOutInterceptors();
087        if(outInterceptors != null) {
088                List<Interceptor<? extends Message>> busOutInterceptors = bus.getOutInterceptors();
089                busOutInterceptors.addAll(outInterceptors);
090        }
091
092
093                HTTPTransportFactory transportFactory = bus.getExtension(HTTPTransportFactory.class);
094        if (transportFactory == null) {
095            throw new IllegalStateException("Failed to locate HTTPTransportFactory extension on Apache CXF Bus");
096        }
097
098        DestinationRegistry destinationRegistry = transportFactory.getRegistry();
099
100
101                this.cxfServletController = new ServletController(destinationRegistry, getCXFServletConfig(
102                this.getServletConfig()), new ServiceListGeneratorServlet(destinationRegistry, bus));
103
104                this.setPublishEvents(false);
105        }
106
107    /**
108     * This is a workaround after upgrading to CXF 2.7.0 whereby we could no longer just call "setHideServiceList" on
109     * the ServletController. Instead, it is now reading this information from the ServletConfig, so wrapping the base
110     * ServletContext to return true or false for hide service list depending on whether or not we are in dev mode.
111     */
112    protected ServletConfig getCXFServletConfig(final ServletConfig baseServletConfig) {
113        // disable handling of URLs ending in /services which display CXF generated service lists if we are not in dev mode
114        final String shouldHide = Boolean.toString(!ConfigContext.getCurrentContextConfig().getDevMode().booleanValue());
115        return new ServletConfig() {
116            private static final String HIDE_SERVICE_LIST_PAGE_PARAM = "hide-service-list-page";
117            @Override
118            public String getServletName() {
119                return baseServletConfig.getServletName();
120            }
121            @Override
122            public ServletContext getServletContext() {
123                return baseServletConfig.getServletContext();
124            }
125            @Override
126            public String getInitParameter(String parameter) {
127                if (HIDE_SERVICE_LIST_PAGE_PARAM.equals(parameter)) {
128                    return shouldHide;
129                }
130                return baseServletConfig.getInitParameter(parameter);
131            }
132            @Override
133            public Enumeration<String> getInitParameterNames() {
134                List<String> initParameterNames = EnumerationUtils.toList(baseServletConfig.getInitParameterNames());
135                initParameterNames.add(HIDE_SERVICE_LIST_PAGE_PARAM);
136                return new Vector<String>(initParameterNames).elements();
137            }
138        };
139    }
140
141    @Override
142        protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
143                if (handler instanceof HttpRequestHandler) {
144                        return new HttpRequestHandlerAdapter();
145                } else if (handler instanceof Controller) {
146                        Object unwrappedHandler = ClassLoaderUtils.unwrapFromProxy(handler);
147                        if (unwrappedHandler instanceof CXFServletControllerAdapter) {
148                                // TODO this just seems weird as this controller is initially null when it's created, does there need to be some synchronization here?
149                                ((CXFServletControllerAdapter)unwrappedHandler).setController(cxfServletController);
150                        }                       
151                        return new SimpleControllerHandlerAdapter();
152                }
153                throw new RiceRuntimeException("handler of type " + handler.getClass().getName() + " is not known and can't be used by " + KSBDispatcherServlet.class.getName());
154        }
155
156    @Override
157        protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
158                return this.httpInvokerHandler.getHandler(request);
159        }
160
161        @Override
162        protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
163                try {
164                        QName serviceName = this.httpInvokerHandler.getServiceNameFromRequest(request);
165                        LOG.info("Caught Exception from service " + serviceName, ex);
166                } catch (Throwable throwable) {
167                        LOG.warn("Caught exception attempting to log exception thrown from remotely accessed service", throwable);
168                }
169                return null;
170        }
171
172        /**
173         * Overrides the service method to replace the request and responses with one which will provide input and output streams for
174         * verifying and signing the data.
175         */
176        @Override
177        protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
178        /* KULRICE-12366:Added some logging of the total time spent making a service call
179             */
180        long startTime = System.currentTimeMillis();
181        try {
182            if (isSecure(request)) {
183                super.service(new SignatureVerifyingRequestWrapper(request), new SignatureSigningResponseWrapper(response));
184            } else {
185                super.service(request, response);
186            }
187        }finally {
188            long endTime = System.currentTimeMillis();
189            LOG.info("Service call took " + (endTime - startTime) + "ms");
190        }
191        }
192
193    @Override
194    protected WebApplicationContext initWebApplicationContext() {
195        setContextConfigLocation(REMOTING_SERVLET_CONFIG_LOCATION);
196        return super.initWebApplicationContext();
197    }
198
199        protected boolean isSecure(HttpServletRequest request) {
200                QName serviceName = this.httpInvokerHandler.getServiceNameFromRequest(request);
201                if (LOG.isDebugEnabled()) {
202                    LOG.debug("Checking service " + serviceName + " for security enabled");
203                }
204                Endpoint endpoint = KsbApiServiceLocator.getServiceBus().getEndpoint(serviceName);
205                if (endpoint == null) {
206                        LOG.error("Attempting to acquire non-existent service " + request.getRequestURI());
207                    throw new RiceRuntimeException("Attempting to acquire non-existent service.");
208                }
209                ServiceConfiguration serviceConfiguration = endpoint.getServiceConfiguration();
210                if (serviceConfiguration instanceof SoapServiceConfiguration) {
211                    return false;
212                }
213                return serviceConfiguration.getBusSecurity();
214        }
215
216}