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}