001 /** 002 * Copyright 2005-2012 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 */ 016 package org.kuali.rice.ksb.messaging.serviceexporters; 017 018 import java.net.URI; 019 import java.net.URL; 020 import java.util.concurrent.ConcurrentHashMap; 021 import java.util.concurrent.ConcurrentMap; 022 023 import javax.xml.namespace.QName; 024 025 import org.apache.commons.lang.StringUtils; 026 import org.apache.cxf.Bus; 027 import org.apache.cxf.endpoint.ServerRegistry; 028 import org.kuali.rice.core.api.config.property.Config; 029 import org.kuali.rice.core.api.config.property.ConfigContext; 030 import org.kuali.rice.ksb.api.bus.ServiceDefinition; 031 032 public class ServiceExportManagerImpl implements ServiceExportManager { 033 034 private final ConcurrentMap<QName, ExportedServiceHolder> exportedServices; 035 private final ServiceNameFinder serviceNameFinder; 036 037 private Bus cxfBus; 038 private ServerRegistry cxfServerRegistry; 039 040 public ServiceExportManagerImpl() { 041 this.exportedServices = new ConcurrentHashMap<QName, ExportedServiceHolder>(); 042 this.serviceNameFinder = new ServiceNameFinder(); 043 } 044 045 @Override 046 public QName getServiceName(String endpointUrl) { 047 return getServiceNameFinder().lookup(endpointUrl); 048 } 049 050 protected ServiceNameFinder getServiceNameFinder() { 051 return serviceNameFinder; 052 } 053 054 @Override 055 public Object getService(QName serviceName) { 056 ExportedServiceHolder holder = exportedServices.get(serviceName); 057 if (holder == null) { 058 return null; 059 } 060 return holder.getExportedService(); 061 } 062 063 @Override 064 public void exportService(ServiceDefinition serviceDefinition) { 065 if (serviceDefinition == null) { 066 throw new IllegalArgumentException("serviceDefinition was null"); 067 } 068 ServiceExporter serviceExporter = ServiceExporterFactory.getServiceExporter(serviceDefinition, cxfBus, cxfServerRegistry); 069 Object exportedService = serviceExporter.exportService(serviceDefinition); 070 exportedServices.put(serviceDefinition.getServiceName(), new ExportedServiceHolder(exportedService, serviceDefinition)); 071 getServiceNameFinder().register(serviceDefinition); 072 } 073 074 @Override 075 public void removeService(QName serviceName) { 076 ExportedServiceHolder exportedServiceHolder = exportedServices.remove(serviceName); 077 getServiceNameFinder().remove(exportedServiceHolder.getServiceDefinition().getEndpointUrl()); 078 } 079 080 protected ConcurrentMap<QName, ExportedServiceHolder> getExportedServices() { 081 return exportedServices; 082 } 083 084 public void setCxfBus(Bus cxfBus) { 085 this.cxfBus = cxfBus; 086 } 087 088 public void setCxfServerRegistry(ServerRegistry cxfServerRegistry) { 089 this.cxfServerRegistry = cxfServerRegistry; 090 } 091 092 protected static class ExportedServiceHolder { 093 094 private final Object exportedService; 095 private final ServiceDefinition serviceDefinition; 096 097 ExportedServiceHolder(Object exportedService, ServiceDefinition serviceDefinition) { 098 this.exportedService = exportedService; 099 this.serviceDefinition = serviceDefinition; 100 } 101 102 public Object getExportedService() { 103 return exportedService; 104 } 105 106 public ServiceDefinition getServiceDefinition() { 107 return serviceDefinition; 108 } 109 110 } 111 112 /** 113 * Looks up service QNameS based on URL StringS. API is Map-like, but non-service specific portions of the 114 * URL are trimmed prior to accessing its internal Map. 115 * 116 * @author Kuali Rice Team (rice.collab@kuali.org) 117 * 118 */ 119 protected static class ServiceNameFinder { 120 121 /** 122 * A service path to service QName map 123 */ 124 private ConcurrentMap<String, QName> servicePathToQName = new ConcurrentHashMap<String, QName>(); 125 126 127 /** 128 * This method trims the endpoint url base ({@link Config#getEndPointUrl()}) base off of the full service URL, e.g. 129 * "http://kuali.edu/kr-dev/remoting/SomeService" -> "SomeService". It makes an effort to do so even if the host 130 * and ip don't match what is in {@link Config#getEndPointUrl()} by stripping host/port info. 131 * 132 * If the service URL contains the configured subpath for RESTful service, additional trimming is done to 133 * isolate the service name from the rest of the url. 134 * 135 * @param url 136 * @return the service specific suffix. If fullServiceUrl doesn't contain the endpoint url base, 137 * fullServiceUrl is returned unmodified. 138 */ 139 private String trimServiceUrlBase(String url) { 140 String trimmedUrl = StringUtils.removeStart(url, ConfigContext.getCurrentContextConfig().getEndPointUrl()); 141 142 if (trimmedUrl.length() == url.length()) { // it didn't contain the endpoint url base. 143 // Perhaps the incoming url has a different host (or the ip) or a different port. 144 // Trim off the host & port, then trim off the common base. 145 URI serviceUri = URI.create(url); 146 URI endpointUrlBase = URI.create(ConfigContext.getCurrentContextConfig().getEndPointUrl()); 147 148 String reqPath = serviceUri.getPath(); 149 String basePath = endpointUrlBase.getPath(); 150 151 trimmedUrl = StringUtils.removeStart(reqPath, basePath); 152 } 153 154 return trimmedUrl; 155 } 156 157 /** 158 * adds a mapping from the service specific portion of the service URL to the service name. 159 * 160 * @param serviceUrl 161 * @param serviceName 162 */ 163 public void register(ServiceDefinition serviceDefinition) { 164 String serviceUrlBase = trimServiceUrlBase(serviceDefinition.getEndpointUrl().toExternalForm()); 165 if (serviceUrlBase.endsWith("/")) { 166 serviceUrlBase = StringUtils.chop(serviceUrlBase); 167 } 168 servicePathToQName.put(serviceUrlBase, serviceDefinition.getServiceName()); 169 } 170 171 /** 172 * removes the mapping (if one exists) for the service specific portion of this url. 173 * 174 * @param serviceUrl 175 */ 176 public void remove(URL endpointUrl) { 177 servicePathToQName.remove(trimServiceUrlBase(endpointUrl.toExternalForm())); 178 } 179 180 /** 181 * gets the QName for the service 182 * 183 * @param serviceUrl 184 * @return 185 */ 186 public QName lookup(String serviceUrl) { 187 String serviceUrlBase = trimServiceUrlBase(serviceUrl); 188 189 // First, make sure we don't have any query params 190 if (serviceUrlBase.length() > 0 && serviceUrlBase.lastIndexOf('?') != -1) { 191 serviceUrlBase = serviceUrlBase.substring(0, serviceUrlBase.lastIndexOf('?')); 192 } 193 194 QName qname = null; 195 // Now, iterate backwards through the url, stripping off pieces until you match -- this should work for rest too 196 while (qname == null) { 197 qname = servicePathToQName.get(serviceUrlBase); 198 199 int lastSeparatorIndex = serviceUrlBase.lastIndexOf('/'); 200 if (lastSeparatorIndex == -1) 201 break; 202 serviceUrlBase = serviceUrlBase.substring(0, lastSeparatorIndex); 203 } 204 205 return qname; 206 } 207 208 } 209 210 }