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 */
020 package org.granite.osgi.metadata;
021
022 import java.io.IOException;
023 import java.io.InputStream;
024 import java.util.ArrayList;
025 import java.util.Dictionary;
026 import java.util.HashSet;
027 import java.util.Hashtable;
028 import java.util.List;
029 import java.util.Set;
030
031 import javax.xml.parsers.DocumentBuilder;
032 import javax.xml.parsers.DocumentBuilderFactory;
033 import javax.xml.parsers.ParserConfigurationException;
034
035 import org.granite.logging.Logger;
036 import org.granite.osgi.classloader.ServiceClassLoader;
037 import org.granite.osgi.constants.OSGIConstants;
038 import org.osgi.framework.Bundle;
039 import org.osgi.framework.BundleContext;
040 import org.osgi.framework.BundleEvent;
041 import org.osgi.framework.SynchronousBundleListener;
042 import org.osgi.service.event.Event;
043 import org.osgi.service.event.EventAdmin;
044 import org.osgi.util.tracker.ServiceTracker;
045 import org.w3c.dom.Document;
046 import org.w3c.dom.Element;
047 import org.w3c.dom.NodeList;
048 import 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 */
063 public 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 }