001 package de.deepamehta.core.impl; 002 003 import de.deepamehta.core.service.SecurityHandler; 004 005 import com.sun.jersey.api.core.DefaultResourceConfig; 006 import com.sun.jersey.api.core.ResourceConfig; 007 import com.sun.jersey.spi.container.servlet.ServletContainer; 008 009 import org.osgi.framework.Bundle; 010 import org.osgi.service.http.HttpContext; 011 import org.osgi.service.http.HttpService; 012 013 import javax.servlet.http.HttpServletRequest; 014 import javax.servlet.http.HttpServletResponse; 015 016 import javax.ws.rs.Path; 017 import javax.ws.rs.WebApplicationException; 018 import javax.ws.rs.core.MultivaluedMap; 019 import javax.ws.rs.core.Response; 020 021 import java.io.IOException; 022 import java.net.URL; 023 import java.util.List; 024 import java.util.Map; 025 import java.util.Set; 026 import java.util.logging.Level; 027 import java.util.logging.Logger; 028 029 030 031 public class WebPublishingService { 032 033 // ------------------------------------------------------------------------------------------------------- Constants 034 035 private static final String ROOT_APPLICATION_PATH = "/"; 036 037 // ---------------------------------------------------------------------------------------------- Instance Variables 038 039 private ResourceConfig rootApplication; 040 private int classCount = 0; // counts DM root resource and provider classes 041 private int singletonCount = 0; // counts DM root resource and provider singletons 042 // Note: we count DM resources separately as Jersey adds its own ones to the application. 043 // Once the total DM resource count reaches 0 the Jersey servlet is unregistered. 044 045 private ServletContainer jerseyServlet; 046 private boolean isJerseyServletRegistered = false; 047 048 private HttpService httpService; 049 050 private EmbeddedService dms; 051 052 private Logger logger = Logger.getLogger(getClass().getName()); 053 054 // ---------------------------------------------------------------------------------------------------- Constructors 055 056 public WebPublishingService(EmbeddedService dms, HttpService httpService) { 057 try { 058 logger.info("Setting up the Web Publishing service"); 059 this.dms = dms; 060 // 061 // create web application 062 this.rootApplication = new DefaultResourceConfig(); 063 // 064 // setup container filters 065 Map<String, Object> properties = rootApplication.getProperties(); 066 properties.put(ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS, new JerseyRequestFilter(dms)); 067 properties.put(ResourceConfig.PROPERTY_CONTAINER_RESPONSE_FILTERS, new JerseyResponseFilter(dms)); 068 // 069 // deploy web application in container 070 this.jerseyServlet = new ServletContainer(rootApplication); 071 this.httpService = httpService; 072 } catch (Exception e) { 073 // unregister...(); // ### TODO? 074 throw new RuntimeException("Setting up the Web Publishing service failed", e); 075 } 076 } 077 078 // ----------------------------------------------------------------------------------------- Package Private Methods 079 080 081 082 // === Web Resources === 083 084 /** 085 * Publishes the /web resources directory of the given bundle to the web. 086 */ 087 WebResources addWebResources(Bundle bundle, String uriNamespace) { 088 try { 089 // Note: registerResources() throws org.osgi.service.http.NamespaceException 090 httpService.registerResources(uriNamespace, "/web", new BundleHTTPContext(bundle)); 091 return new WebResources(uriNamespace); 092 } catch (Exception e) { 093 throw new RuntimeException(e); 094 } 095 } 096 097 void removeWebResources(WebResources webResources) { 098 httpService.unregister(webResources.uriNamespace); 099 } 100 101 // --- 102 103 /** 104 * Publishes a directory of the server's file system to the web. 105 */ 106 WebResources addWebResources(String directoryPath, String uriNamespace, SecurityHandler securityHandler) { 107 try { 108 // Note: registerResources() throws org.osgi.service.http.NamespaceException 109 httpService.registerResources(uriNamespace, "/", new DirectoryHTTPContext(directoryPath, securityHandler)); 110 return new WebResources(uriNamespace); 111 } catch (Exception e) { 112 throw new RuntimeException(e); 113 } 114 } 115 116 117 118 // === REST Resources === 119 120 /** 121 * Note: synchronizing this method prevents creation of multiple Jersey servlet instances due to parallel plugin 122 * initialization. 123 * 124 * @param resources the set of root resource and provider singletons, may be empty. 125 * @param providerClasses the set of root resource and provider classes, may be empty. 126 */ 127 synchronized RestResource addRestResource(List<Object> resources, List<Class<?>> providerClasses) { 128 addSingletons(resources); 129 addClasses(providerClasses); 130 logResourceInfo(); 131 // 132 // Note: we must register the Jersey servlet lazily, that is not before any root resources are added. 133 // An "empty" application would fail (com.sun.jersey.api.container.ContainerException: 134 // The ResourceConfig instance does not contain any root resource classes). 135 if (!isJerseyServletRegistered) { 136 // Note: we must not register the servlet as long as no root resources are added yet. 137 // A plugin may contain just provider classes. 138 if (hasRootResources()) { 139 registerJerseyServlet(); 140 } 141 } else { 142 reloadJerseyServlet(); 143 } 144 // 145 return new RestResource(resources, providerClasses); 146 } 147 148 synchronized void removeRestResource(RestResource restResource) { 149 removeSingletons(restResource.resources); 150 removeClasses(restResource.providerClasses); 151 logResourceInfo(); 152 // 153 // Note: once all root resources are removed we must unregister the Jersey servlet. 154 // Reloading it with an "empty" application would fail (com.sun.jersey.api.container.ContainerException: 155 // The ResourceConfig instance does not contain any root resource classes). 156 if (!hasRootResources()) { 157 unregisterJerseyServlet(); 158 } else { 159 reloadJerseyServlet(); 160 } 161 } 162 163 // --- 164 165 boolean isRootResource(Object object) { 166 return getUriNamespace(object) != null; 167 } 168 169 String getUriNamespace(Object object) { 170 Path path = object.getClass().getAnnotation(Path.class); 171 return path != null ? path.value() : null; 172 } 173 174 // ------------------------------------------------------------------------------------------------- Private Methods 175 176 177 178 // === Jersey Servlet === 179 180 private Set<Class<?>> getClasses() { 181 return rootApplication.getClasses(); 182 } 183 184 private Set<Object> getSingletons() { 185 return rootApplication.getSingletons(); 186 } 187 188 // --- 189 190 private void addClasses(List<Class<?>> classes) { 191 getClasses().addAll(classes); 192 classCount += classes.size(); 193 } 194 195 private void addSingletons(List<Object> singletons) { 196 getSingletons().addAll(singletons); 197 singletonCount += singletons.size(); 198 } 199 200 // --- 201 202 private void removeClasses(List<Class<?>> classes) { 203 getClasses().removeAll(classes); 204 classCount -= classes.size(); 205 } 206 207 private void removeSingletons(List<Object> singletons) { 208 getSingletons().removeAll(singletons); 209 singletonCount -= singletons.size(); 210 } 211 212 // --- 213 214 private boolean hasRootResources() { 215 return singletonCount > 0; 216 } 217 218 private void logResourceInfo() { 219 logger.fine("##### DM Classes: " + classCount + ", All: " + getClasses().size() + " " + getClasses()); 220 logger.fine("##### DM Singletons: " + singletonCount + ", All: " + getSingletons().size() + " " + 221 getSingletons()); 222 } 223 224 // --- 225 226 private void registerJerseyServlet() { 227 try { 228 logger.fine("########## Registering Jersey servlet at HTTP service (URI namespace=\"" + 229 ROOT_APPLICATION_PATH + "\")"); 230 // Note: registerServlet() throws javax.servlet.ServletException 231 httpService.registerServlet(ROOT_APPLICATION_PATH, jerseyServlet, null, null); 232 isJerseyServletRegistered = true; 233 } catch (Exception e) { 234 // unregister...(); // ### TODO? 235 throw new RuntimeException("Registering Jersey servlet at HTTP service failed", e); 236 } 237 } 238 239 private void unregisterJerseyServlet() { 240 logger.fine("########## Unregistering Jersey servlet at HTTP service (URI namespace=\"" + 241 ROOT_APPLICATION_PATH + "\")"); 242 httpService.unregister(ROOT_APPLICATION_PATH); 243 isJerseyServletRegistered = false; 244 } 245 246 // --- 247 248 private void reloadJerseyServlet() { 249 logger.fine("##### Reloading Jersey servlet"); 250 jerseyServlet.reload(); 251 } 252 253 254 255 // === Resource Request Filter === 256 257 private boolean resourceRequestFilter(HttpServletRequest request, HttpServletResponse response) throws IOException { 258 try { 259 dms.fireEvent(CoreEvent.RESOURCE_REQUEST_FILTER, request); 260 return true; 261 } catch (WebApplicationException e) { 262 sendError(response, e.getResponse()); 263 return false; 264 } catch (Exception e) { 265 logger.log(Level.SEVERE, "Resource request filtering failed for " + request.getRequestURI(), e); 266 response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); 267 return false; 268 } 269 } 270 271 private void sendError(HttpServletResponse servletResponse, Response response) throws IOException { 272 // transfer headers 273 MultivaluedMap<String, Object> metadata = response.getMetadata(); 274 for (String header : metadata.keySet()) { 275 for (Object value : metadata.get(header)) { 276 servletResponse.addHeader(header, (String) value); 277 } 278 } 279 // 280 servletResponse.sendError(response.getStatus(), (String) response.getEntity()); // throws IOException 281 } 282 283 284 285 // ------------------------------------------------------------------------------------------------- Private Classes 286 287 private class BundleHTTPContext implements HttpContext { 288 289 private Bundle bundle; 290 291 private BundleHTTPContext(Bundle bundle) { 292 this.bundle = bundle; 293 } 294 295 // --- 296 297 @Override 298 public URL getResource(String name) { 299 // map resource name "/" to "/index.html" 300 if (name.equals("web/")) { 301 name = "/web/index.html"; 302 } 303 // 304 return bundle.getResource(name); 305 } 306 307 @Override 308 public String getMimeType(String name) { 309 return null; 310 } 311 312 @Override 313 public boolean handleSecurity(HttpServletRequest request, HttpServletResponse response) 314 throws java.io.IOException { 315 return resourceRequestFilter(request, response); 316 } 317 } 318 319 private class DirectoryHTTPContext implements HttpContext { 320 321 private String directoryPath; 322 private SecurityHandler securityHandler; 323 324 private DirectoryHTTPContext(String directoryPath, SecurityHandler securityHandler) { 325 this.directoryPath = directoryPath; 326 this.securityHandler = securityHandler; 327 } 328 329 // --- 330 331 @Override 332 public URL getResource(String name) { 333 try { 334 URL url = new URL("file:" + directoryPath + "/" + name); // throws java.net.MalformedURLException 335 logger.info("### Mapping resource name \"" + name + "\" to URL \"" + url + "\""); 336 return url; 337 } catch (Exception e) { 338 throw new RuntimeException("Mapping resource name \"" + name + "\" to URL failed", e); 339 } 340 } 341 342 @Override 343 public String getMimeType(String name) { 344 return null; 345 } 346 347 @Override 348 public boolean handleSecurity(HttpServletRequest request, HttpServletResponse response) 349 throws java.io.IOException { 350 boolean doService = resourceRequestFilter(request, response); 351 if (doService) { 352 if (securityHandler != null) { 353 return securityHandler.handleSecurity(request, response); 354 } else { 355 return true; 356 } 357 } 358 return false; 359 } 360 } 361 }