package org.argeo.cms.jcr.internal; import static org.argeo.cms.osgi.DataModelNamespace.CMS_DATA_MODEL_NAMESPACE; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.TreeMap; import org.argeo.api.cms.CmsLog; import org.argeo.cms.osgi.DataModelNamespace; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleEvent; import org.osgi.framework.BundleListener; import org.osgi.framework.wiring.BundleCapability; import org.osgi.framework.wiring.BundleWire; import org.osgi.framework.wiring.BundleWiring; class DataModels implements BundleListener { private final static CmsLog log = CmsLog.getLog(DataModels.class); private Map dataModels = new TreeMap<>(); public DataModels(BundleContext bc) { for (Bundle bundle : bc.getBundles()) processBundle(bundle, null); bc.addBundleListener(this); } public List getNonAbstractDataModels() { List res = new ArrayList<>(); for (String name : dataModels.keySet()) { DataModel dataModel = dataModels.get(name); if (!dataModel.isAbstract()) res.add(dataModel); } // TODO reorder? return res; } @Override public void bundleChanged(BundleEvent event) { if (event.getType() == Bundle.RESOLVED) { processBundle(event.getBundle(), null); } else if (event.getType() == Bundle.UNINSTALLED) { BundleWiring wiring = event.getBundle().adapt(BundleWiring.class); List providedDataModels = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE); if (providedDataModels.size() == 0) return; for (BundleCapability bundleCapability : providedDataModels) { dataModels.remove(bundleCapability.getAttributes().get(DataModelNamespace.NAME)); } } } protected void processBundle(Bundle bundle, List scannedBundles) { if (scannedBundles != null && scannedBundles.contains(bundle)) throw new IllegalStateException("Cycle in CMS data model requirements for " + bundle); BundleWiring wiring = bundle.adapt(BundleWiring.class); if (wiring == null) { int bundleState = bundle.getState(); if (bundleState != Bundle.INSTALLED && bundleState != Bundle.UNINSTALLED) {// ignore unresolved bundles log.warn("Bundle " + bundle.getSymbolicName() + " #" + bundle.getBundleId() + " (" + bundle.getLocation() + ") cannot be adapted to a wiring"); } else { if (log.isTraceEnabled()) log.warn("Bundle " + bundle.getSymbolicName() + " is not resolved."); } return; } List providedDataModels = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE); if (providedDataModels.size() == 0) return; List requiredDataModels = wiring.getRequiredWires(CMS_DATA_MODEL_NAMESPACE); // process requirements first for (BundleWire bundleWire : requiredDataModels) { List nextScannedBundles = new ArrayList<>(); if (scannedBundles != null) nextScannedBundles.addAll(scannedBundles); nextScannedBundles.add(bundle); Bundle providerBundle = bundleWire.getProvider().getBundle(); processBundle(providerBundle, nextScannedBundles); } for (BundleCapability bundleCapability : providedDataModels) { String name = (String) bundleCapability.getAttributes().get(DataModelNamespace.NAME); assert name != null; if (!dataModels.containsKey(name)) { DataModel dataModel = new DataModel(name, bundleCapability, requiredDataModels); dataModels.put(dataModel.getName(), dataModel); } } } /** Return a negative depth if dataModel is required by ref, 0 otherwise. */ static int required(DataModel ref, DataModel dataModel, int depth) { for (DataModel dm : ref.getRequired()) { if (dm.equals(dataModel))// found here return depth - 1; int d = required(dm, dataModel, depth - 1); if (d != 0)// found deeper return d; } return 0;// not found } class DataModel { private final String name; private final boolean abstrct; // private final boolean standalone; private final String cnd; private final List required; private DataModel(String name, BundleCapability bundleCapability, List requiredDataModels) { assert CMS_DATA_MODEL_NAMESPACE.equals(bundleCapability.getNamespace()); this.name = name; Map attrs = bundleCapability.getAttributes(); abstrct = KernelUtils.asBoolean((String) attrs.get(DataModelNamespace.ABSTRACT)); // standalone = KernelUtils.asBoolean((String) // attrs.get(DataModelNamespace.CAPABILITY_STANDALONE_ATTRIBUTE)); cnd = (String) attrs.get(DataModelNamespace.CND); List req = new ArrayList<>(); for (BundleWire wire : requiredDataModels) { String requiredDataModelName = (String) wire.getCapability().getAttributes() .get(DataModelNamespace.NAME); assert requiredDataModelName != null; DataModel requiredDataModel = dataModels.get(requiredDataModelName); if (requiredDataModel == null) throw new IllegalStateException("No required data model " + requiredDataModelName); req.add(requiredDataModel); } required = Collections.unmodifiableList(req); } public String getName() { return name; } public boolean isAbstract() { return abstrct; } // public boolean isStandalone() { // return !isAbstract(); // } public String getCnd() { return cnd; } public List getRequired() { return required; } // @Override // public int compareTo(DataModel o) { // if (equals(o)) // return 0; // int res = required(this, o, 0); // if (res != 0) // return res; // // the other way round // res = required(o, this, 0); // if (res != 0) // return -res; // return 0; // } @Override public int hashCode() { return name.hashCode(); } @Override public boolean equals(Object obj) { if (obj instanceof DataModel) return ((DataModel) obj).name.equals(name); return false; } @Override public String toString() { return "Data model " + name; } } }