1 // ======================================================================== 2 // Copyright (c) 2009-2010 Intalio, Inc. 3 // ------------------------------------------------------------------------ 4 // All rights reserved. This program and the accompanying materials 5 // are made available under the terms of the Eclipse Public License v1.0 6 // and Apache License v2.0 which accompanies this distribution. 7 // The Eclipse Public License is available at 8 // http://www.eclipse.org/legal/epl-v10.html 9 // The Apache License v2.0 is available at 10 // http://www.opensource.org/licenses/apache2.0.php 11 // You may elect to redistribute this code under either of these licenses. 12 // ======================================================================== 13 package org.eclipse.jetty.osgi.boot.internal.webapp; 14 15 import java.net.URL; 16 import java.util.Dictionary; 17 18 import org.eclipse.jetty.osgi.boot.JettyBootstrapActivator; 19 import org.eclipse.jetty.osgi.boot.OSGiWebappConstants; 20 import org.eclipse.jetty.util.log.Log; 21 import org.osgi.framework.Bundle; 22 import org.osgi.framework.BundleEvent; 23 import org.osgi.util.tracker.BundleTracker; 24 import org.osgi.util.tracker.BundleTrackerCustomizer; 25 26 27 /** 28 * Support bundles that declare the webapp directly through headers in their 29 * manifest. 30 * <p> 31 * Those headers will define a new WebApplication: 32 * <ul> 33 * <li>Web-ContextPath</li> 34 * <li>Jetty-WarFolderPath</li> 35 * </ul> 36 * </p> 37 * <p> 38 * Those headers will define a new app started via a jetty-context or a list of 39 * them. ',' column is the separator between the various context files. 40 * <ul> 41 * <li>Jetty-ContextFilePath</li> 42 * </ul> 43 * </p> 44 * And generate a jetty WebAppContext or another ContextHandler then registers 45 * it as service. Kind of simpler than declarative services and their xml files. 46 * Also avoid having the contributing bundle depend on jetty's package for 47 * WebApp. 48 * 49 * @author hmalphettes 50 */ 51 public class WebBundleTrackerCustomizer implements BundleTrackerCustomizer { 52 53 54 /** 55 * A bundle is being added to the <code>BundleTracker</code>. 56 * 57 * <p> 58 * This method is called before a bundle which matched the search parameters 59 * of the <code>BundleTracker</code> is added to the 60 * <code>BundleTracker</code>. This method should return the object to be 61 * tracked for the specified <code>Bundle</code>. The returned object is 62 * stored in the <code>BundleTracker</code> and is available from the 63 * {@link BundleTracker#getObject(Bundle) getObject} method. 64 * 65 * @param bundle The <code>Bundle</code> being added to the 66 * <code>BundleTracker</code>. 67 * @param event The bundle event which caused this customizer method to be 68 * called or <code>null</code> if there is no bundle event associated 69 * with the call to this method. 70 * @return The object to be tracked for the specified <code>Bundle</code> 71 * object or <code>null</code> if the specified <code>Bundle</code> 72 * object should not be tracked. 73 */ 74 public Object addingBundle(Bundle bundle, BundleEvent event) 75 { 76 if (bundle.getState() == Bundle.ACTIVE) 77 { 78 boolean isWebBundle = register(bundle); 79 return isWebBundle ? bundle : null; 80 } 81 else if (bundle.getState() == Bundle.STOPPING) 82 { 83 unregister(bundle); 84 } 85 else 86 { 87 //we should not be called in that state as 88 //we are registered only for ACTIVE and STOPPING 89 } 90 return null; 91 } 92 93 /** 94 * A bundle tracked by the <code>BundleTracker</code> has been modified. 95 * 96 * <p> 97 * This method is called when a bundle being tracked by the 98 * <code>BundleTracker</code> has had its state modified. 99 * 100 * @param bundle The <code>Bundle</code> whose state has been modified. 101 * @param event The bundle event which caused this customizer method to be 102 * called or <code>null</code> if there is no bundle event associated 103 * with the call to this method. 104 * @param object The tracked object for the specified bundle. 105 */ 106 public void modifiedBundle(Bundle bundle, BundleEvent event, 107 Object object) 108 { 109 //nothing the web-bundle was already track. something changed. 110 //we only reload the webapps if the bundle is stopped and restarted. 111 // System.err.println(bundle.getSymbolicName()); 112 if (bundle.getState() == Bundle.STOPPING || bundle.getState() == Bundle.ACTIVE) 113 { 114 unregister(bundle); 115 } 116 if (bundle.getState() == Bundle.ACTIVE) 117 { 118 register(bundle); 119 } 120 } 121 122 /** 123 * A bundle tracked by the <code>BundleTracker</code> has been removed. 124 * 125 * <p> 126 * This method is called after a bundle is no longer being tracked by the 127 * <code>BundleTracker</code>. 128 * 129 * @param bundle The <code>Bundle</code> that has been removed. 130 * @param event The bundle event which caused this customizer method to be 131 * called or <code>null</code> if there is no bundle event associated 132 * with the call to this method. 133 * @param object The tracked object for the specified bundle. 134 */ 135 public void removedBundle(Bundle bundle, BundleEvent event, 136 Object object) 137 { 138 unregister(bundle); 139 } 140 141 142 /** 143 * @param bundle 144 * @return true if this bundle in indeed a web-bundle. 145 */ 146 private boolean register(Bundle bundle) 147 { 148 Dictionary<?, ?> dic = bundle.getHeaders(); 149 String warFolderRelativePath = (String)dic.get(OSGiWebappConstants.JETTY_WAR_FOLDER_PATH); 150 if (warFolderRelativePath != null) 151 { 152 String contextPath = (String)dic.get(OSGiWebappConstants.RFC66_WEB_CONTEXTPATH); 153 if (contextPath == null || !contextPath.startsWith("/")) 154 { 155 Log.warn("The manifest header '" + OSGiWebappConstants.JETTY_WAR_FOLDER_PATH + 156 ": " + warFolderRelativePath + "' in the bundle " + bundle.getSymbolicName() + 157 " is not valid: there is no Web-ContextPath defined in the manifest."); 158 return false; 159 } 160 // create the corresponding service and publish it in the context of 161 // the contributor bundle. 162 try 163 { 164 JettyBootstrapActivator.registerWebapplication(bundle,warFolderRelativePath,contextPath); 165 return true; 166 } 167 catch (Throwable e) 168 { 169 Log.warn("Starting the web-bundle " + bundle.getSymbolicName() + " threw an exception.", e); 170 return true;//maybe it did not work maybe it did. safer to track this bundle. 171 } 172 } 173 else if (dic.get(OSGiWebappConstants.JETTY_CONTEXT_FILE_PATH) != null) 174 { 175 String contextFileRelativePath = (String)dic.get(OSGiWebappConstants.JETTY_CONTEXT_FILE_PATH); 176 if (contextFileRelativePath == null) 177 { 178 // nothing to register here. 179 return false; 180 } 181 // support for multiple webapps in the same bundle: 182 String[] pathes = contextFileRelativePath.split(",;"); 183 for (String path : pathes) 184 { 185 try 186 { 187 JettyBootstrapActivator.registerContext(bundle,path.trim()); 188 } 189 catch (Throwable e) 190 { 191 // TODO Auto-generated catch block 192 e.printStackTrace(); 193 } 194 } 195 return true; 196 } 197 else 198 { 199 // support for OSGi-RFC66; disclaimer, no access to the actual 200 // (draft) of the spec: just a couple of posts on the 201 // world-wide-web. 202 URL rfc66Webxml = bundle.getEntry("/WEB-INF/web.xml"); 203 if (rfc66Webxml == null && dic.get(OSGiWebappConstants.RFC66_WEB_CONTEXTPATH) == null) 204 { 205 return false;// no webapp in here 206 } 207 // this is risky: should we make sure that there is no classes and 208 // jars directly available 209 // at the root of the of the bundle: otherwise they are accessible 210 // through the browser. we should enforce that the whole classpath 211 // is 212 // pointing to files and folders inside WEB-INF. We should 213 // filter-out 214 // META-INF too 215 String rfc66ContextPath = getWebContextPath(bundle,dic); 216 try 217 { 218 JettyBootstrapActivator.registerWebapplication(bundle,".",rfc66ContextPath); 219 return true; 220 } 221 catch (Throwable e) 222 { 223 // TODO Auto-generated catch block 224 e.printStackTrace(); 225 return true;//maybe it did not work maybe it did. safer to track this bundle. 226 } 227 } 228 } 229 230 private String getWebContextPath(Bundle bundle, Dictionary<?, ?> dic) 231 { 232 String rfc66ContextPath = (String)dic.get(OSGiWebappConstants.RFC66_WEB_CONTEXTPATH); 233 if (rfc66ContextPath == null) 234 { 235 // extract from the last token of the bundle's location: 236 // (really ? 237 // could consider processing the symbolic name as an alternative 238 // the location will often reflect the version. 239 // maybe this is relevant when the file is a war) 240 String location = bundle.getLocation(); 241 String toks[] = location.replace('\\','/').split("/"); 242 rfc66ContextPath = toks[toks.length - 1]; 243 // remove .jar, .war etc: 244 int lastDot = rfc66ContextPath.lastIndexOf('.'); 245 if (lastDot != -1) 246 { 247 rfc66ContextPath = rfc66ContextPath.substring(0,lastDot); 248 } 249 } 250 if (!rfc66ContextPath.startsWith("/")) 251 { 252 rfc66ContextPath = "/" + rfc66ContextPath; 253 } 254 return rfc66ContextPath; 255 } 256 257 private void unregister(Bundle bundle) 258 { 259 // nothing to do: when the bundle is stopped, each one of its service 260 // reference is also stopped and that is what we use to stop the 261 // corresponding 262 // webapps registered in that bundle. 263 } 264 265 266 267 268 }