1 /*******************************************************************************
2  * Copyright (c) 2005, 2015 Cognos Incorporated, IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     Cognos Incorporated - initial API and implementation
13  *     IBM Corporation - bug fixes and enhancements
14  *     Raymond Augé <raymond.auge@liferay.com> - Bug 436698
15  *******************************************************************************/
16 
17 package org.eclipse.equinox.http.servlet.internal;
18 
19 import java.util.*;
20 import java.util.Map.Entry;
21 import java.util.concurrent.ConcurrentHashMap;
22 import java.util.concurrent.ConcurrentMap;
23 import javax.servlet.*;
24 import javax.servlet.http.HttpServlet;
25 import org.eclipse.equinox.http.servlet.ExtendedHttpService;
26 import org.eclipse.equinox.http.servlet.internal.servlet.ProxyServlet;
27 import org.eclipse.equinox.http.servlet.internal.util.*;
28 import org.osgi.framework.*;
29 import org.osgi.service.http.HttpService;
30 import org.osgi.service.http.runtime.HttpServiceRuntime;
31 import org.osgi.service.http.runtime.HttpServiceRuntimeConstants;
32 import org.osgi.util.tracker.ServiceTracker;
33 import org.osgi.util.tracker.ServiceTrackerCustomizer;
34 
35 public class Activator
36 	implements BundleActivator, ServiceTrackerCustomizer<HttpServlet, HttpTuple> {
37 
38 	private static final String DEFAULT_SERVICE_DESCRIPTION = "Equinox Servlet Bridge"; //$NON-NLS-1$
39 	private static final String DEFAULT_SERVICE_VENDOR = "Eclipse.org"; //$NON-NLS-1$
40 	private static final String PROP_GLOBAL_WHITEBOARD = "equinox.http.global.whiteboard"; //$NON-NLS-1$
41 	public static final String UNIQUE_SERVICE_ID = "equinox.http.id"; //$NON-NLS-1$
42 	private static final String[] HTTP_SERVICES_CLASSES = new String[] {
43 		HttpService.class.getName(), ExtendedHttpService.class.getName()
44 	};
45 
46 	private static volatile BundleContext context;
47 	private static ConcurrentMap<ProxyServlet, Object> registrations =
48 		new ConcurrentHashMap<ProxyServlet, Object>();
49 
50 	private ServiceTracker<HttpServlet, HttpTuple> serviceTracker;
51 
addProxyServlet(ProxyServlet proxyServlet)52 	public static void addProxyServlet(ProxyServlet proxyServlet) {
53 		Object previousRegistration = registrations.putIfAbsent(
54 			proxyServlet, proxyServlet);
55 		BundleContext currentContext = context;
56 		try {
57 			if (!(previousRegistration instanceof ServiceRegistration) &&
58 				(currentContext != null)) {
59 				ServiceRegistration<HttpServlet> serviceRegistration =
60 					currentContext.registerService(
61 						HttpServlet.class, proxyServlet,
62 						new Hashtable<String, Object>());
63 
64 				registrations.put(proxyServlet, serviceRegistration);
65 			}
66 		} catch (IllegalStateException ex) {
67 			//If the currentContext is no longer valid.
68 			return;
69 		}
70 	}
71 
unregisterHttpService(ProxyServlet proxyServlet)72 	public static void unregisterHttpService(ProxyServlet proxyServlet) {
73 		Object registration = registrations.remove(proxyServlet);
74 
75 		if (registration instanceof ServiceRegistration) {
76 			ServiceRegistration<?> serviceRegistration =
77 				(ServiceRegistration<?>)registration;
78 
79 			serviceRegistration.unregister();
80 		}
81 	}
82 
start(BundleContext bundleContext)83 	public void start(BundleContext bundleContext) throws Exception {
84 		context = bundleContext;
85 
86 		processRegistrations();
87 
88 		serviceTracker = new ServiceTracker<HttpServlet, HttpTuple>(
89 			bundleContext, HttpServlet.class, this);
90 
91 		serviceTracker.open();
92 	}
93 
stop(BundleContext bundleContext)94 	public void stop(BundleContext bundleContext) throws Exception {
95 		serviceTracker.close();
96 		serviceTracker = null;
97 		context = null;
98 	}
99 
addingService( ServiceReference<HttpServlet> serviceReference)100 	public HttpTuple addingService(
101 		ServiceReference<HttpServlet> serviceReference) {
102 		BundleContext currentContext = context;
103 		if (currentContext == null) {
104 			return null;
105 		}
106 
107 		try {
108 			HttpServlet httpServlet = currentContext.getService(serviceReference);
109 
110 			if (!(httpServlet instanceof ProxyServlet)) {
111 				currentContext.ungetService(serviceReference);
112 				return null;
113 			}
114 
115 			ProxyServlet proxyServlet = (ProxyServlet)httpServlet;
116 
117 			ServletConfig servletConfig = proxyServlet.getServletConfig();
118 			ServletContext servletContext = servletConfig.getServletContext();
119 
120 			Dictionary<String, Object> serviceProperties =
121 				new Hashtable<String, Object>(3);
122 
123 			Enumeration<String> initparameterNames =
124 				servletConfig.getInitParameterNames();
125 
126 			while (initparameterNames.hasMoreElements()) {
127 				String name = initparameterNames.nextElement();
128 
129 				serviceProperties.put(
130 					name, servletConfig.getInitParameter(name));
131 			}
132 
133 			if (serviceProperties.get(Constants.SERVICE_VENDOR) == null) {
134 				serviceProperties.put(
135 					Constants.SERVICE_VENDOR, DEFAULT_SERVICE_VENDOR);
136 			}
137 
138 			if (serviceProperties.get(Constants.SERVICE_DESCRIPTION) == null) {
139 				serviceProperties.put(
140 					Constants.SERVICE_DESCRIPTION, DEFAULT_SERVICE_DESCRIPTION);
141 			}
142 
143 			Object httpServiceEndpointObj = serviceProperties.get(HttpServiceRuntimeConstants.HTTP_SERVICE_ENDPOINT);
144 
145 			if (httpServiceEndpointObj == null) {
146 				String[] httpServiceEndpoints = getHttpServiceEndpoints(
147 					serviceProperties, servletContext, servletConfig.getServletName());
148 
149 				serviceProperties.put(
150 					HttpServiceRuntimeConstants.HTTP_SERVICE_ENDPOINT,
151 					httpServiceEndpoints);
152 			}
153 			else {
154 				List<String> httpServiceEndpoints = new ArrayList<String>();
155 
156 				String contextPath = servletContext.getContextPath();
157 
158 				for (String httpServiceEndpoint : StringPlus.from(httpServiceEndpointObj)) {
159 					if (!httpServiceEndpoint.startsWith(Const.HTTP.concat(":")) && !httpServiceEndpoint.startsWith(contextPath)) { //$NON-NLS-1$
160 						httpServiceEndpoint = contextPath + httpServiceEndpoint;
161 					}
162 
163 					httpServiceEndpoints.add(httpServiceEndpoint);
164 				}
165 
166 				serviceProperties.put(
167 					HttpServiceRuntimeConstants.HTTP_SERVICE_ENDPOINT,
168 					httpServiceEndpoints);
169 			}
170 
171 			// need a unique id for our service to match old HttpService HttpContext
172 			serviceProperties.put(UNIQUE_SERVICE_ID, new Random().nextLong());
173 			// white board support
174 			// determine if the system bundle context should be used:
175 			boolean useSystemContext = Boolean.valueOf(currentContext.getProperty(PROP_GLOBAL_WHITEBOARD));
176 			BundleContext trackingContext = useSystemContext ? currentContext.getBundle(Constants.SYSTEM_BUNDLE_LOCATION).getBundleContext() : currentContext;
177 			HttpServiceRuntimeImpl httpServiceRuntime = new HttpServiceRuntimeImpl(
178 				trackingContext, currentContext, servletContext, serviceProperties);
179 			httpServiceRuntime.open();
180 
181 			proxyServlet.setHttpServiceRuntimeImpl(httpServiceRuntime);
182 
183 			// imperative API support;
184 			// the http service must be registered first so we can get its service id
185 			HttpServiceFactory httpServiceFactory = new HttpServiceFactory(httpServiceRuntime);
186 			ServiceRegistration<?> hsfRegistration = currentContext.registerService(
187 				HTTP_SERVICES_CLASSES, httpServiceFactory, serviceProperties);
188 
189 			serviceProperties.put(HttpServiceRuntimeConstants.HTTP_SERVICE_ID, Collections.singletonList(hsfRegistration.getReference().getProperty(Constants.SERVICE_ID)));
190 
191 			ServiceRegistration<HttpServiceRuntime> hsrRegistration =
192 				currentContext.registerService(
193 					HttpServiceRuntime.class, httpServiceRuntime,
194 					serviceProperties);
195 
196 			httpServiceRuntime.setHsrRegistration(hsrRegistration);
197 
198 			return new HttpTuple(
199 				proxyServlet, httpServiceFactory, hsfRegistration,
200 				httpServiceRuntime, hsrRegistration);
201 		} catch (IllegalStateException ex) {
202 			//If the currentContext is no longer valid.
203 			return null;
204 		}
205 	}
206 
modifiedService( ServiceReference<HttpServlet> serviceReference, HttpTuple httpTuple)207 	public void modifiedService(
208 		ServiceReference<HttpServlet> serviceReference, HttpTuple httpTuple) {
209 
210 		removedService(serviceReference, httpTuple);
211 		addingService(serviceReference);
212 	}
213 
removedService( ServiceReference<HttpServlet> serviceReference, HttpTuple httpTuple)214 	public void removedService(
215 		ServiceReference<HttpServlet> serviceReference, HttpTuple httpTuple) {
216 		BundleContext currentContext = context;
217 		if (currentContext != null) {
218 			try {
219 				currentContext.ungetService(serviceReference);
220 				httpTuple.destroy();
221 			} catch (IllegalStateException ex) {
222 				//If the currentContext is no longer valid.
223 				return;
224 			}
225 		}
226 	}
227 
getHttpServiceEndpoints( Dictionary<String, Object> serviceProperties, ServletContext servletContext, String servletName)228 	private String[] getHttpServiceEndpoints(
229 		Dictionary<String, Object> serviceProperties, ServletContext servletContext, String servletName) {
230 
231 		List<String> httpServiceEndpoints = new ArrayList<String>();
232 
233 		String contextPath = (String)serviceProperties.get(Const.CONTEXT_PATH);
234 
235 		if ((contextPath != null)) {
236 			String httpHost = (String)serviceProperties.get(Const.HTTP_HOST);
237 			String httpPort = (String)serviceProperties.get(Const.HTTP_PORT);
238 
239 			if (httpPort != null) {
240 				if (httpHost == null) {
241 					String endpoint = assembleEndpoint(Const.HTTP, Const.LOCALHOST, httpPort, contextPath);
242 					httpServiceEndpoints.add(endpoint);
243 				}
244 				else {
245 					String endpoint = assembleEndpoint(Const.HTTP, httpHost, httpPort, contextPath);
246 					httpServiceEndpoints.add(endpoint);
247 				}
248 			}
249 
250 			String httpsHost = (String)serviceProperties.get(Const.HTTPS_HOST);
251 			String httpsPort = (String)serviceProperties.get(Const.HTTPS_PORT);
252 
253 			if (httpsPort != null) {
254 				if (httpsHost == null) {
255 					String endpoint = assembleEndpoint(Const.HTTPS, Const.LOCALHOST, httpsPort, contextPath);
256 					httpServiceEndpoints.add(endpoint);
257 				}
258 				else {
259 					String endpoint = assembleEndpoint(Const.HTTPS, httpHost, httpsPort, contextPath);
260 					httpServiceEndpoints.add(endpoint);
261 				}
262 			}
263 
264 			if (!httpServiceEndpoints.isEmpty()) {
265 				return httpServiceEndpoints.toArray(new String[0]);
266 			}
267 		}
268 
269 		contextPath = servletContext.getContextPath();
270 
271 		ServletRegistration servletRegistration = null;
272 		try {
273 			servletRegistration = servletContext.getServletRegistration(servletName);
274 		} catch (UnsupportedOperationException e) {
275 			StringBuilder sb = new StringBuilder();
276 			sb.append("Could not find the servlet registration for the servlet: "); //$NON-NLS-1$
277 			sb.append(servletName);
278 			sb.append(" The Http Service will not be able to locate it's root path."); //$NON-NLS-1$
279 			sb.append(" This can be overcome by specifying an init-param with name 'osgi.http.endpoint'"); //$NON-NLS-1$
280 			sb.append(" and value equal to the servlet mapping minus the glob character '*'."); //$NON-NLS-1$
281 			servletContext.log(sb.toString());
282 		}
283 
284 		if (servletRegistration == null) {
285 			return new String[0];
286 		}
287 
288 		Collection<String> mappings = servletRegistration.getMappings();
289 
290 		for (String mapping : mappings) {
291 			if (mapping.indexOf('/') == 0) {
292 				if (mapping.charAt(mapping.length() - 1) == '*') {
293 					mapping = mapping.substring(0, mapping.length() - 1);
294 
295 					if ((mapping.length() > 1) &&
296 						(mapping.charAt(mapping.length() - 1) != '/')) {
297 
298 						mapping += '/';
299 					}
300 				}
301 
302 				httpServiceEndpoints.add(contextPath + mapping);
303 			}
304 		}
305 
306 		return httpServiceEndpoints.toArray(new String[0]);
307 	}
308 
assembleEndpoint(String protocol, String host, String port, String contextPath)309 	private String assembleEndpoint(String protocol, String host, String port, String contextPath) {
310 		StringBuilder sb = new StringBuilder();
311 		sb.append(protocol);
312 		sb.append(Const.PROTOCOL);
313 		sb.append(host);
314 		sb.append(':');
315 		sb.append(port);
316 		sb.append(contextPath);
317 		if (sb.charAt(sb.length() - 1) != '/') {
318 			sb.append('/');
319 		}
320 		return sb.toString();
321 	}
322 
processRegistrations()323 	private void processRegistrations() {
324 		Iterator<Entry<ProxyServlet, Object>> iterator =
325 			registrations.entrySet().iterator();
326 		BundleContext currentContext = context;
327 		if (currentContext == null) {
328 			return;
329 		}
330 
331 		while (iterator.hasNext()) {
332 			Entry<ProxyServlet, Object> entry = iterator.next();
333 			ProxyServlet proxyServlet = entry.getKey();
334 			Object value = entry.getValue();
335 
336 			try {
337 				if (!(value instanceof ServiceRegistration)) {
338 					ServiceRegistration<HttpServlet> serviceRegistration =
339 						currentContext.registerService(
340 							HttpServlet.class, proxyServlet,
341 							new Hashtable<String, Object>());
342 
343 					entry.setValue(serviceRegistration);
344 				}
345 			} catch (IllegalStateException ex) {
346 				//If the currentContext is no longer valid.
347 				return;
348 			}
349 		}
350 	}
351 
352 }