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