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