001/* 002 GRANITE DATA SERVICES 003 Copyright (C) 2011 GRANITE DATA SERVICES S.A.S. 004 005 This file is part of Granite Data Services. 006 007 Granite Data Services is free software; you can redistribute it and/or modify 008 it under the terms of the GNU Library General Public License as published by 009 the Free Software Foundation; either version 2 of the License, or (at your 010 option) any later version. 011 012 Granite Data Services is distributed in the hope that it will be useful, but 013 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 014 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License 015 for more details. 016 017 You should have received a copy of the GNU Library General Public License 018 along with this library; if not, see <http://www.gnu.org/licenses/>. 019*/ 020package org.granite.osgi.metadata; 021 022import java.io.IOException; 023import java.io.InputStream; 024import java.util.ArrayList; 025import java.util.Dictionary; 026import java.util.HashSet; 027import java.util.Hashtable; 028import java.util.List; 029import java.util.Set; 030 031import javax.xml.parsers.DocumentBuilder; 032import javax.xml.parsers.DocumentBuilderFactory; 033import javax.xml.parsers.ParserConfigurationException; 034 035import org.granite.logging.Logger; 036import org.granite.osgi.classloader.ServiceClassLoader; 037import org.granite.osgi.constants.OSGIConstants; 038import org.osgi.framework.Bundle; 039import org.osgi.framework.BundleContext; 040import org.osgi.framework.BundleEvent; 041import org.osgi.framework.SynchronousBundleListener; 042import org.osgi.service.event.Event; 043import org.osgi.service.event.EventAdmin; 044import org.osgi.util.tracker.ServiceTracker; 045import org.w3c.dom.Document; 046import org.w3c.dom.Element; 047import org.w3c.dom.NodeList; 048import org.xml.sax.SAXException; 049/** 050 * Parse the Manifest of a bundle to load the GraniteDS-Service config file 051 * i.e. GraniteDS-Service: /resources/granite-osgi.xml 052 * <pre> 053 * <graniteds> 054 * <services> 055 * <service packages="org.graniteds.services.security" /> 056 * </services> 057 * </graniteds> 058 * </pre> 059 * @see SynchronousBundleListener 060 * @author <a href="mailto:gembin@gmail.com">gembin@gmail.com</a> 061 * @since 1.1.0 062 */ 063public class ManifestMetadataParser implements SynchronousBundleListener { 064 065 private static final Logger log = Logger.getLogger(ManifestMetadataParser.class); 066 067 /** 068 * the seperator of the packages which will be scanned 069 * i.e. org.granite.service.test,org.granite.service.testa.* 070 */ 071 private static final String SEPERATOR=","; 072 /** 073 * Element in the metadata file which will contain any number of service Elements 074 */ 075 private static final String SERVICES="services"; 076 /** 077 * Element in the metadata file 078 * i.e. <service packages="org.granite.osgi.test,org.granite.osgi.test.*" /> 079 */ 080 private static final String SERVICE="service"; 081 /** 082 * An attribute of 'service' Element in the metadata file 083 * packages of a bundle which contain some GraniteDS dataservice classes 084 * these packages will be scanned by this parser 085 * i.e. packages="org.granite.osgi.test,org.granite.osgi.test.*" 086 */ 087 private static final String PROP_PACKAGES="packages"; 088 /** 089 * the property key in the MANFEST.MF to specify the metadata 090 * i.e. GraniteDS-Service: GraniteDS-INF/domain-config/granite-osgi.xml 091 * Parser only scans the bundles who have the this property key presented 092 */ 093 private static final String GRANITEDS_SERVICE="GraniteDS-Service"; 094 /** 095 * the classloader which is used to load the classes in the bundles 096 * which will provide the dataservices 097 */ 098 ServiceClassLoader classLoader; 099 /** 100 * EventAdmin is used to send a event to the EventHandler 101 * 'ServicesConfig' when a bundle is changed 102 */ 103 ServiceTracker eventAdminTracker; 104 /** 105 * bundle context of Granite OSGi bundle 106 */ 107 BundleContext context; 108 /** 109 * a set of qualifed Granite dataservice classes which will 110 * be registered or unregistered during the runtime 111 */ 112 Set<Class<?>> classes; 113 /** 114 * the metadata xml file path 115 * i.e. GraniteDS-INF/granite-osgi.xml 116 */ 117 String granitedsMeta; 118 /** 119 * metadata processing thread 120 */ 121 static DocumentBuilder documentBuilder; 122 private final MetadataProcessor processorThread = new MetadataProcessor(); 123 /** 124 * Constructor 125 * @param context 126 */ 127 public ManifestMetadataParser(BundleContext context) { 128 this.context = context; 129 } 130 131 private void setGraniteMeta(String granitedsMeta) { 132 this.granitedsMeta = granitedsMeta; 133 } 134 135 private EventAdmin getEventAdmin(){ 136 return (EventAdmin) eventAdminTracker.getService(); 137 } 138 /** 139 * broadcast service change 140 * @param eventTopic 141 */ 142 private void broadcastServicesChanged(String eventTopic){ 143 if(classes!=null && classes.size()>0){ 144 Dictionary<String, Object> properties = new Hashtable<String, Object>(); 145 properties.put(OSGIConstants.SERVICE_CLASS_SET, classes); 146 EventAdmin eadmin=getEventAdmin(); 147 if(eadmin!=null){ 148 eadmin.sendEvent(new Event(eventTopic,properties)); 149 }else{ 150 if(log.isErrorEnabled()) 151 log.error("EventAdmin is unavailable, cannot broadcast Event!!!"); 152 } 153 } 154 } 155 156 /** 157 * parse the metadata 158 * 159 * @param bundle 160 * @param eventTopic 161 */ 162 private void parseMetadata(Bundle bundle,String eventTopic){ 163 if(log.isInfoEnabled()) 164 log.info(GRANITEDS_SERVICE+":"+granitedsMeta); 165 classLoader.setBundle(bundle); 166 DocumentBuilder builder = getDocumentBuilder(); 167 try { 168 if (builder != null) { 169 if(granitedsMeta==null || "".equals(granitedsMeta))return; 170 InputStream is=bundle.getEntry(granitedsMeta).openStream(); 171 if(is==null)return; 172 Document doc=builder.parse(is); 173 Element servicesNode=(Element) doc.getElementsByTagName(SERVICES).item(0); 174 NodeList services=servicesNode.getElementsByTagName(SERVICE); 175 for(int i=0;i<services.getLength();i++){ 176 Element service= (Element) services.item(i); 177 String[] servicePackages=service.getAttribute(PROP_PACKAGES).split(SEPERATOR); 178 if(servicePackages!=null){ 179 classes.addAll(classLoader.loadClasses(servicePackages)); 180 }else{ 181 throw new RuntimeException("Invalid Service at "+i); 182 } 183 } 184 broadcastServicesChanged(eventTopic); 185 } 186 } catch (SAXException e) { 187 log.error(e, "Could not parse metadata"); 188 } catch (IOException e) { 189 log.error(e, "Could not parse metadata"); 190 } 191 } 192 /** 193 * @return DocumentBuilder 194 */ 195 private synchronized static DocumentBuilder getDocumentBuilder() { 196 try { 197 if(documentBuilder==null) 198 documentBuilder= DocumentBuilderFactory.newInstance().newDocumentBuilder(); 199 } catch (ParserConfigurationException e) { 200 log.error(e, "Could not get document builder"); 201 } 202 //DocumentBuilder is reset to the same state as when it was created 203 documentBuilder.reset(); 204 return documentBuilder; 205 } 206 /** 207 * start to parse Metadata 208 */ 209 public void start() { 210 classLoader=new ServiceClassLoader(); 211 eventAdminTracker=new ServiceTracker(context,EventAdmin.class.getName(),null); 212 eventAdminTracker.open(); 213 new Thread(processorThread).start(); 214 synchronized (this) { 215 context.addBundleListener(this);// listen to any changes in bundles. 216 } 217 if(classes==null){ 218 classes=new HashSet<Class<?>>(); 219 }else{ 220 classes.clear(); 221 } 222 } 223 /** 224 * stop Metadata Parser 225 */ 226 public void stop(){ 227 eventAdminTracker.close(); 228 processorThread.stop(); // Stop the thread processing bundles. 229 context.removeBundleListener(this); 230 classLoader=null; 231 classes=null; 232 } 233 /** 234 * @param bundle 235 * @return true if the bundle has the property key 'GraniteDS-Service' presented and with value setted 236 */ 237 private boolean hasDataService(Bundle bundle){ 238 if(bundle==null)return false; 239 Object gsd=bundle.getHeaders().get(GRANITEDS_SERVICE); 240 if(gsd!=null) 241 setGraniteMeta(gsd.toString()); 242 return (gsd!=null || "".equals(gsd)); 243 } 244 /* 245 * (non-Javadoc) 246 * @see org.osgi.framework.BundleListener#bundleChanged(org.osgi.framework.BundleEvent) 247 */ 248 public void bundleChanged(BundleEvent event) { 249 Bundle bundle=event.getBundle(); 250 // ignore own bundle GraniteDS OSGi bundle 251 // and GraniteDS unpowered bundle(property key 'GraniteDS-Service' is not presented) 252 if(context.getBundle()==bundle || !hasDataService(bundle))return; 253 switch (event.getType()) { 254 case BundleEvent.STARTED: 255 // Put the bundle in the queue to register dataservices 256 processorThread.addBundle(bundle); 257 break; 258 case BundleEvent.STOPPING: 259 // Put the bundle in the queue to unregister dataservices 260 processorThread.removeBundle(bundle); 261 break; 262 default: 263 break; 264 } 265 } 266 /** 267 * 268 */ 269 private class MetadataProcessor implements Runnable{ 270 private boolean hasStarted=true; 271 private List<Bundle> bundles = new ArrayList<Bundle>(); 272 private List<Bundle> removedBundles = new ArrayList<Bundle>(); 273 private synchronized void addBundle(Bundle bundle){ 274 bundles.add(bundle); 275 notifyAll(); // Notify the thread to force the process. 276 } 277 private synchronized void removeBundle(Bundle bundle){ 278 bundles.remove(bundle); 279 removedBundles.add(bundle); 280 notifyAll(); // Notify the thread to force the process. 281 } 282 /** 283 * Stops the processor thread. 284 */ 285 public synchronized void stop() { 286 hasStarted = false; 287 bundles.clear(); 288 notifyAll(); 289 } 290 public void run() { 291 while (hasStarted) { 292 Bundle bundle=null; 293 Bundle removeBundle=null; 294 synchronized (this) { 295 while (hasStarted && bundles.isEmpty() && removedBundles.isEmpty()) { 296 try { 297 //log.info("waiting..."); 298 wait(); 299 } catch (InterruptedException e) { 300 // Interruption, re-check the condition 301 } 302 } 303 if (!hasStarted) 304 return; // The thread must be stopped immediately. 305 306 // The bundle list is not empty, get the bundle. 307 // The bundle object is collected inside the synchronized block to avoid 308 // concurrent modification. However the real process is made outside the 309 // mutual exclusion area 310 if(bundles.size()>0) 311 bundle = bundles.remove(0); 312 if(removedBundles.size()>0) 313 removeBundle = removedBundles.remove(0); 314 } 315 if(bundle!=null){ 316 if(log.isInfoEnabled()) 317 log.info("Processing AddService for bundle: %s", bundle.getSymbolicName()); 318 parseMetadata(bundle,OSGIConstants.TOPIC_GDS_ADD_SERVICE); 319 } 320 if(removeBundle!=null){ 321 if(log.isInfoEnabled()) 322 log.info("Processing RemoveService for bundle: %s", removeBundle.getSymbolicName()); 323 parseMetadata(removeBundle,OSGIConstants.TOPIC_GDS_REMOVE_SERVICE); 324 } 325 } 326 } 327 } 328 329}