001/* 002 * (C) Copyright 2006-2010 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Bogdan Stefanescu 016 * Thierry Delprat 017 */ 018package org.nuxeo.apidoc.introspection; 019 020import java.io.File; 021import java.io.FileInputStream; 022import java.io.FileNotFoundException; 023import java.io.FileReader; 024import java.io.IOException; 025import java.io.InputStream; 026import java.io.InputStreamReader; 027import java.io.Reader; 028import java.io.Writer; 029import java.lang.annotation.Annotation; 030import java.lang.reflect.Field; 031import java.util.ArrayList; 032import java.util.Collection; 033import java.util.Enumeration; 034import java.util.HashMap; 035import java.util.List; 036import java.util.Map; 037import java.util.PropertyResourceBundle; 038import java.util.zip.ZipEntry; 039import java.util.zip.ZipFile; 040 041import javax.xml.parsers.DocumentBuilder; 042import javax.xml.parsers.DocumentBuilderFactory; 043import javax.xml.parsers.ParserConfigurationException; 044import javax.xml.xpath.XPath; 045import javax.xml.xpath.XPathConstants; 046import javax.xml.xpath.XPathException; 047import javax.xml.xpath.XPathFactory; 048 049import org.apache.commons.logging.Log; 050import org.apache.commons.logging.LogFactory; 051import org.nuxeo.apidoc.api.ComponentInfo; 052import org.nuxeo.apidoc.documentation.DocumentationHelper; 053import org.nuxeo.common.utils.FileUtils; 054import org.nuxeo.ecm.core.api.NuxeoException; 055import org.nuxeo.osgi.BundleImpl; 056import org.nuxeo.runtime.RuntimeService; 057import org.nuxeo.runtime.api.Framework; 058import org.nuxeo.runtime.model.Extension; 059import org.nuxeo.runtime.model.ExtensionPoint; 060import org.nuxeo.runtime.model.RegistrationInfo; 061import org.osgi.framework.Bundle; 062import org.w3c.dom.Document; 063import org.xml.sax.SAXException; 064 065/** 066 * The entry point to the server runtime introspection To build a description of the current running server you need to 067 * create a {@link ServerInfo} object using the method {@link #build(String, String)}. 068 * <p> 069 * Example 070 * 071 * <pre> 072 * ServerInfo info = ServerInfo.build(); 073 * </pre> 074 * 075 * The server name and version will be fetched form the runtime properties: <code>org.nuxeo.ecm.product.name</code> and 076 * <code>org.nuxeo.ecm.product.version</code> If you ant to use another name and version just call 077 * {@link #build(String, String)} instead to build your server information. 078 * <p> 079 * After building a <code>ServerInfo</code> object you can start browsing the bundles deployed on the server by calling 080 * {@link #getBundles()} or fetch a specific bundle given its symbolic name {@link #getBundle(String)}. 081 * <p> 082 * To write down the server information as XML use {@link #toXML(Writer)} and to read it back use 083 * {@link #fromXML(Reader)}. 084 * <p> 085 * Example: 086 * 087 * <pre> 088 * ServerInfo info = ServerInfo.build(); 089 * BundleInfo binfo =info.getBundle("org.nuxeo.runtime"); 090 * System.out.println("Bundle Id: "+binfo.getBundleId()); 091 * System.out.println("File Name: "+binfo.getFileName()); 092 * System.out.println("Manifest: "+ binfo.getManifest()); 093 * for (ComponentInfo cinfo : binfo.getComponents()) { 094 * System.out.println("Component: "+cinfo.getName()); 095 * System.out.println(cinfo.getDocumentation()); 096 * // find extension points provided by this component 097 * for (ExtensionPointInfo xpi : cinfo.getExtensionPoints()) { 098 * System.out.println("Extension point: "+xpi.getName()); 099 * System.out.println("Accepted contribution classes: "+Arrays.asList(xpi.getTypes())); 100 * // find contributed extensions to this extension point: 101 * 102 * } 103 * // find contribution provided by this component 104 * for (ExtensionInfo xi : cinfo.getExtensions()) { 105 * System.out.println("Extension: "+xi.getId()+" to "+xi.getExtensionPoint()); 106 * System.out.println(xi.getDocumentation()); 107 * ... 108 * } 109 * } 110 * </pre> 111 */ 112public class ServerInfo { 113 114 private static final Log log = LogFactory.getLog(ServerInfo.class); 115 116 public static final String META_INF_MANIFEST_MF = "META-INF/MANIFEST.MF"; 117 118 public static final String POM_XML = "pom.xml"; 119 120 public static final String POM_PROPERTIES = "pom.properties"; 121 122 protected static final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); 123 124 protected static final XPathFactory xpathFactory = XPathFactory.newInstance(); 125 126 protected final String name; 127 128 protected final String version; 129 130 protected final Map<String, BundleInfoImpl> bundles = new HashMap<String, BundleInfoImpl>(); 131 132 protected final List<Class<?>> allSpi = new ArrayList<Class<?>>(); 133 134 public ServerInfo(String name, String version) { 135 this.name = name; 136 this.version = version; 137 } 138 139 public String getName() { 140 return name; 141 } 142 143 public String getVersion() { 144 return version; 145 } 146 147 public Collection<BundleInfoImpl> getBundles() { 148 return bundles.values(); 149 } 150 151 public void addBundle(BundleInfoImpl bundle) { 152 bundles.put(bundle.getId(), bundle); 153 } 154 155 public void addBundle(Collection<BundleInfoImpl> bundles) { 156 for (BundleInfoImpl bundle : bundles) { 157 this.bundles.put(bundle.getId(), bundle); 158 } 159 } 160 161 public BundleInfoImpl getBundle(String id) { 162 return bundles.get(id); 163 } 164 165 public static ServerInfo build() { 166 return build(Framework.getProperty("org.nuxeo.ecm.product.name", "Nuxeo"), 167 Framework.getProperty("org.nuxeo.ecm.product.version", "unknown")); 168 } 169 170 protected static BundleInfoImpl computeBundleInfo(Bundle bundle) { 171 RuntimeService runtime = Framework.getRuntime(); 172 BundleInfoImpl binfo = new BundleInfoImpl(bundle.getSymbolicName()); 173 binfo.setFileName(runtime.getBundleFile(bundle).getName()); 174 binfo.setLocation(bundle.getLocation()); 175 if (!(bundle instanceof BundleImpl)) { 176 return binfo; 177 } 178 BundleImpl nxBundle = (BundleImpl) bundle; 179 File jarFile = nxBundle.getBundleFile().getFile(); 180 if (jarFile == null) { 181 return binfo; 182 } 183 try { 184 if (jarFile.isDirectory()) { 185 // directory: run from Eclipse in unit tests 186 // .../nuxeo-runtime/nuxeo-runtime/bin 187 // or sometimes 188 // .../nuxeo-runtime/nuxeo-runtime/bin/main 189 File manifest = new File(jarFile, META_INF_MANIFEST_MF); 190 if (manifest.exists()) { 191 InputStream is = new FileInputStream(manifest); 192 String mf = FileUtils.read(is); 193 binfo.setManifest(mf); 194 } 195 // find and parse pom.xml 196 File up = new File(jarFile, ".."); 197 File pom = new File(up, POM_XML); 198 if (!pom.exists()) { 199 pom = new File(new File(up, ".."), POM_XML); 200 if (!pom.exists()) { 201 pom = null; 202 } 203 } 204 if (pom != null) { 205 DocumentBuilder b = documentBuilderFactory.newDocumentBuilder(); 206 Document doc = b.parse(new FileInputStream(pom)); 207 XPath xpath = xpathFactory.newXPath(); 208 String groupId = (String) xpath.evaluate("//project/groupId", doc, XPathConstants.STRING); 209 if ("".equals(groupId)) { 210 groupId = (String) xpath.evaluate("//project/parent/groupId", doc, XPathConstants.STRING); 211 } 212 String artifactId = (String) xpath.evaluate("//project/artifactId", doc, XPathConstants.STRING); 213 if ("".equals(artifactId)) { 214 artifactId = (String) xpath.evaluate("//project/parent/artifactId", doc, XPathConstants.STRING); 215 } 216 String version = (String) xpath.evaluate("//project/version", doc, XPathConstants.STRING); 217 if ("".equals(version)) { 218 version = (String) xpath.evaluate("//project/parent/version", doc, XPathConstants.STRING); 219 } 220 binfo.setArtifactId(artifactId); 221 binfo.setGroupId(groupId); 222 binfo.setArtifactVersion(version); 223 } 224 } else { 225 ZipFile zFile = new ZipFile(jarFile); 226 ZipEntry mfEntry = zFile.getEntry(META_INF_MANIFEST_MF); 227 if (mfEntry != null) { 228 InputStream mfStream = zFile.getInputStream(mfEntry); 229 String mf = FileUtils.read(mfStream); 230 binfo.setManifest(mf); 231 } 232 Enumeration<? extends ZipEntry> entries = zFile.entries(); 233 while (entries.hasMoreElements()) { 234 ZipEntry entry = entries.nextElement(); 235 if (entry.getName().endsWith(POM_PROPERTIES)) { 236 InputStream is = zFile.getInputStream(entry); 237 PropertyResourceBundle prb = new PropertyResourceBundle(is); 238 String groupId = prb.getString("groupId"); 239 String artifactId = prb.getString("artifactId"); 240 String version = prb.getString("version"); 241 binfo.setArtifactId(artifactId); 242 binfo.setGroupId(groupId); 243 binfo.setArtifactVersion(version); 244 is.close(); 245 break; 246 } 247 } 248 zFile.close(); 249 zFile = new ZipFile(jarFile); 250 EmbeddedDocExtractor.extractEmbeddedDoc(zFile, binfo); 251 zFile.close(); 252 } 253 } catch (IOException | ParserConfigurationException | SAXException | XPathException | NuxeoException e) { 254 log.error(e, e); 255 } 256 return binfo; 257 } 258 259 protected static List<Class<?>> getSPI(Class<?> klass) { 260 List<Class<?>> spi = new ArrayList<Class<?>>(); 261 for (Field field : klass.getDeclaredFields()) { 262 String cName = field.getType().getCanonicalName(); 263 if (cName.startsWith("org.nuxeo")) { 264 // remove XObjects 265 Class<?> fieldClass = field.getType(); 266 Annotation[] annotations = fieldClass.getDeclaredAnnotations(); 267 if (annotations.length == 0) { 268 spi.add(fieldClass); 269 } 270 } 271 } 272 return spi; 273 } 274 275 public static ServerInfo build(String name, String version) { 276 RuntimeService runtime = Framework.getRuntime(); 277 ServerInfo server = new ServerInfo(name, version); 278 BundleInfoImpl configVirtualBundle = new BundleInfoImpl("org.nuxeo.ecm.config"); 279 server.addBundle(configVirtualBundle); 280 281 Map<String, ExtensionPointInfoImpl> xpRegistry = new HashMap<String, ExtensionPointInfoImpl>(); 282 List<ExtensionInfoImpl> contribRegistry = new ArrayList<ExtensionInfoImpl>(); 283 284 Collection<RegistrationInfo> registrations = runtime.getComponentManager().getRegistrations(); 285 286 for (RegistrationInfo ri : registrations) { 287 String cname = ri.getName().getName(); 288 Bundle bundle = ri.getContext().getBundle(); 289 BundleInfoImpl binfo = null; 290 291 if (bundle == null) { 292 binfo = configVirtualBundle; 293 } else { 294 String symName = bundle.getSymbolicName(); 295 if (symName == null) { 296 log.error("No symbolic name found for bundle " + cname); 297 continue; 298 } 299 // avoids duplicating/overriding the bundles 300 if (server.bundles.containsKey(bundle.getSymbolicName())) { 301 binfo = server.bundles.get(bundle.getSymbolicName()); 302 } else { 303 binfo = computeBundleInfo(bundle); 304 } 305 } 306 307 // TODO binfo.setRequirements(requirements); 308 ComponentInfoImpl component = new ComponentInfoImpl(binfo, cname); 309 if (ri.getExtensionPoints() != null) { 310 for (ExtensionPoint xp : ri.getExtensionPoints()) { 311 ExtensionPointInfoImpl xpinfo = new ExtensionPointInfoImpl(component, xp.getName()); 312 Class<?>[] ctypes = xp.getContributions(); 313 String[] descriptors = new String[ctypes.length]; 314 315 for (int i = 0; i < ctypes.length; i++) { 316 descriptors[i] = ctypes[i].getCanonicalName(); 317 List<Class<?>> spi = getSPI(ctypes[i]); 318 xpinfo.addSpi(spi); 319 server.allSpi.addAll(spi); 320 } 321 xpinfo.setDescriptors(descriptors); 322 xpinfo.setDocumentation(xp.getDocumentation()); 323 xpRegistry.put(xpinfo.getId(), xpinfo); 324 component.addExtensionPoint(xpinfo); 325 } 326 } 327 328 component.setXmlFileUrl(ri.getXmlFileUrl()); 329 330 if (ri.getProvidedServiceNames() != null) { 331 for (String serviceName : ri.getProvidedServiceNames()) { 332 component.addService(serviceName); 333 } 334 } 335 336 if (ri.getExtensions() != null) { 337 for (Extension xt : ri.getExtensions()) { 338 ExtensionInfoImpl xtinfo = new ExtensionInfoImpl(component, xt.getExtensionPoint()); 339 xtinfo.setTargetComponentName(xt.getTargetComponent()); 340 xtinfo.setContribution(xt.getContributions()); 341 xtinfo.setDocumentation(xt.getDocumentation()); 342 xtinfo.setXml(DocumentationHelper.secureXML(xt.toXML())); 343 344 contribRegistry.add(xtinfo); 345 346 component.addExtension(xtinfo); 347 } 348 } 349 350 component.setComponentClass(ri.getImplementation()); 351 component.setDocumentation(ri.getDocumentation()); 352 353 binfo.addComponent(component); 354 server.addBundle(binfo); 355 } 356 357 // now register the bundles that contains no components !!! 358 Bundle[] allbundles = runtime.getContext().getBundle().getBundleContext().getBundles(); 359 for (Bundle bundle : allbundles) { 360 if (!server.bundles.containsKey(bundle.getSymbolicName())) { 361 BundleInfoImpl bi = computeBundleInfo(bundle); 362 server.addBundle(bi); 363 } 364 } 365 366 // associate contrib to XP 367 for (ExtensionInfoImpl contrib : contribRegistry) { 368 String xp = contrib.getExtensionPoint(); 369 ExtensionPointInfoImpl ep = xpRegistry.get(xp); 370 if (ep != null) { 371 ep.addExtension(contrib); 372 } 373 } 374 375 return server; 376 } 377 378 public void toXML(Writer writer) throws IOException { 379 XMLWriter xw = new XMLWriter(writer, 4); 380 xw.start(); 381 xw.element("server").attr("name", name).attr("version", version).start(); 382 for (BundleInfoImpl bundle : bundles.values()) { 383 xw.element("bundle").attr("id", bundle.bundleId).start(); 384 xw.element("fileName").content(bundle.fileName); 385 // TODO requirements 386 for (ComponentInfo component : bundle.getComponents()) { 387 xw.element("component").attr("id", component.getId()).start(); 388 // for (ExtensionPointInfo xp : component.getExtensionPoints()) 389 // { } 390 // for (ExtensionInfo xt : component.getExtensions()) { } 391 xw.close(); 392 } 393 xw.close(); 394 } 395 xw.close(); 396 xw.close(); 397 } 398 399 public static ServerInfo fromXML(File file) throws IOException { 400 InputStreamReader reader = new FileReader(file); 401 try { 402 return fromXML(reader); 403 } finally { 404 reader.close(); 405 } 406 } 407 408 public static ServerInfo fromXML(Reader reader) { 409 return null; 410 } 411 412 public List<Class<?>> getAllSpi() { 413 return allSpi; 414 } 415 416}