/* * Copyright: (c) 2004-2011 Mayo Foundation for Medical Education and * Research (MFMER). All rights reserved. MAYO, MAYO CLINIC, and the * triple-shield Mayo logo are trademarks and service marks of MFMER. * * Except as contained in the copyright notice above, or as used to identify * MFMER as the author of this software, the trade names, trademarks, service * marks, or product names of the copyright holder shall not be used in * advertising, promotion or otherwise in connection with this software without * prior written authorization of the copyright holder. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package edu.mayo.cts2.framework.webapp.rest.osgi; import java.lang.reflect.Field; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import javax.annotation.Resource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.osgi.framework.ServiceReference; import org.osgi.util.tracker.ServiceTracker; import org.osgi.util.tracker.ServiceTrackerCustomizer; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationContext; import org.springframework.context.Lifecycle; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.stereotype.Controller; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.handler.AbstractUrlHandlerMapping; import org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping; import edu.mayo.cts2.framework.core.plugin.ExtensionPoint; import edu.mayo.cts2.framework.core.plugin.PluginManager; import edu.mayo.cts2.framework.webapp.rest.extensions.controller.ControllerProvider; public class OsgiAnnotationHandlerMapping extends DefaultAnnotationHandlerMapping implements ExtensionPoint, InitializingBean { protected Log log = LogFactory.getLog(getClass()); private final Map<Class<?>, RequestMapping> cachedMappings = new HashMap<Class<?>, RequestMapping>(); @Resource private PluginManager pluginManager; protected String[] determineUrlsForOsgiService(Object osgiController) { Class<?> handlerType = osgiController.getClass(); RequestMapping mapping = osgiController.getClass().getAnnotation(RequestMapping.class); if (mapping != null) { // @RequestMapping found at type level this.cachedMappings.put(handlerType, mapping); Set<String> urls = new LinkedHashSet<String>(); String[] typeLevelPatterns = mapping.value(); if (typeLevelPatterns.length > 0) { // @RequestMapping specifies paths at type level String[] methodLevelPatterns = determineUrlsForHandlerMethods(handlerType, true); for (String typeLevelPattern : typeLevelPatterns) { if (!typeLevelPattern.startsWith("/")) { typeLevelPattern = "/" + typeLevelPattern; } boolean hasEmptyMethodLevelMappings = false; for (String methodLevelPattern : methodLevelPatterns) { if (methodLevelPattern == null) { hasEmptyMethodLevelMappings = true; } else { String combinedPattern = getPathMatcher().combine(typeLevelPattern, methodLevelPattern); addUrlsForPath(urls, combinedPattern); } } if (hasEmptyMethodLevelMappings || org.springframework.web.servlet.mvc.Controller.class.isAssignableFrom(handlerType)) { addUrlsForPath(urls, typeLevelPattern); } } return StringUtils.toStringArray(urls); } else { // actual paths specified by @RequestMapping at method level return determineUrlsForHandlerMethods(handlerType, false); } } else if (AnnotationUtils.findAnnotation(handlerType, Controller.class) != null) { // @RequestMapping to be introspected at method level return determineUrlsForHandlerMethods(handlerType, false); } else { return null; } } @Override public void afterPropertiesSet() throws Exception { this.pluginManager.registerExtensionPoint(this); } @Override public Class<?> getServiceClass() { return ControllerProvider.class; } @Override public void setServiceTracker(ServiceTracker serviceTracker) { // } @Override public ServiceTrackerCustomizer addServiceTrackerCustomizer() { return new ServiceTrackerCustomizer(){ /* (non-Javadoc) * @see org.osgi.util.tracker.ServiceTrackerCustomizer#addingService(org.osgi.framework.ServiceReference) */ @Override public Object addingService(ServiceReference reference) { Object service = reference.getBundle().getBundleContext().getService(reference); String[] urls = OsgiAnnotationHandlerMapping.this.determineUrlsForOsgiService(service); for(String url : urls){ OsgiAnnotationHandlerMapping.this.registerHandler(url, service); } return service; } @Override public void modifiedService(ServiceReference reference, Object service) { // } @Override public void removedService(ServiceReference reference, Object service) { ApplicationContext context = OsgiAnnotationHandlerMapping.this.getApplicationContext(); if(context instanceof Lifecycle){ boolean running = ((Lifecycle) context).isRunning(); if(running){ this.clearAndDetect(); } else { log.warn("Application is not running - no REST URL Annotations will be processed."); } } else { log.warn("ApplicationContext does not implement the Lifecycle interface... cannot determine if it is shutting down."); this.clearAndDetect(); } } private void clearAndDetect(){ OsgiAnnotationHandlerMapping.this.clearHandlerMappings(); OsgiAnnotationHandlerMapping.this.detectHandlers(); } }; } /** * Clear all handler mappings. Spring made the 'handlerMap' unmodifiable, * so we must resort to reflection. */ protected void clearHandlerMappings(){ Field field; try { field = AbstractUrlHandlerMapping.class.getDeclaredField("handlerMap"); field.setAccessible(true); } catch (Exception e) { throw new RuntimeException(e); } ReflectionUtils.setField( field, OsgiAnnotationHandlerMapping.this, new LinkedHashMap<String, Object>()); } }