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