View Javadoc
1   package org.argeo.cms.internal.kernel;
2   
3   import static org.argeo.api.DataModelNamespace.CMS_DATA_MODEL_NAMESPACE;
4   
5   import java.util.ArrayList;
6   import java.util.Collections;
7   import java.util.List;
8   import java.util.Map;
9   import java.util.TreeMap;
10  
11  import org.apache.commons.logging.Log;
12  import org.apache.commons.logging.LogFactory;
13  import org.argeo.api.DataModelNamespace;
14  import org.argeo.cms.CmsException;
15  import org.osgi.framework.Bundle;
16  import org.osgi.framework.BundleContext;
17  import org.osgi.framework.BundleEvent;
18  import org.osgi.framework.BundleListener;
19  import org.osgi.framework.wiring.BundleCapability;
20  import org.osgi.framework.wiring.BundleWire;
21  import org.osgi.framework.wiring.BundleWiring;
22  
23  class DataModels implements BundleListener {
24  	private final static Log log = LogFactory.getLog(DataModels.class);
25  
26  	private Map<String, DataModel> dataModels = new TreeMap<>();
27  
28  	public DataModels(BundleContext bc) {
29  		for (Bundle bundle : bc.getBundles())
30  			processBundle(bundle, null);
31  		bc.addBundleListener(this);
32  	}
33  
34  	public List<DataModel> getNonAbstractDataModels() {
35  		List<DataModel> res = new ArrayList<>();
36  		for (String name : dataModels.keySet()) {
37  			DataModel dataModel = dataModels.get(name);
38  			if (!dataModel.isAbstract())
39  				res.add(dataModel);
40  		}
41  		// TODO reorder?
42  		return res;
43  	}
44  
45  	@Override
46  	public void bundleChanged(BundleEvent event) {
47  		if (event.getType() == Bundle.RESOLVED) {
48  			processBundle(event.getBundle(), null);
49  		} else if (event.getType() == Bundle.UNINSTALLED) {
50  			BundleWiring wiring = event.getBundle().adapt(BundleWiring.class);
51  			List<BundleCapability> providedDataModels = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE);
52  			if (providedDataModels.size() == 0)
53  				return;
54  			for (BundleCapability bundleCapability : providedDataModels) {
55  				dataModels.remove(bundleCapability.getAttributes().get(DataModelNamespace.NAME));
56  			}
57  		}
58  
59  	}
60  
61  	protected void processBundle(Bundle bundle, List<Bundle> scannedBundles) {
62  		if (scannedBundles != null && scannedBundles.contains(bundle))
63  			throw new IllegalStateException("Cycle in CMS data model requirements for " + bundle);
64  		BundleWiring wiring = bundle.adapt(BundleWiring.class);
65  		if (wiring == null) {
66  			int bundleState = bundle.getState();
67  			if (bundleState != Bundle.INSTALLED && bundleState != Bundle.UNINSTALLED) {// ignore unresolved bundles
68  				log.warn("Bundle " + bundle.getSymbolicName() + " #" + bundle.getBundleId() + " ("
69  						+ bundle.getLocation() + ") cannot be adapted to a wiring");
70  			} else {
71  				if (log.isTraceEnabled())
72  					log.warn("Bundle " + bundle.getSymbolicName() + " is not resolved.");
73  			}
74  			return;
75  		}
76  		List<BundleCapability> providedDataModels = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE);
77  		if (providedDataModels.size() == 0)
78  			return;
79  		List<BundleWire> requiredDataModels = wiring.getRequiredWires(CMS_DATA_MODEL_NAMESPACE);
80  		// process requirements first
81  		for (BundleWire bundleWire : requiredDataModels) {
82  			List<Bundle> nextScannedBundles = new ArrayList<>();
83  			if (scannedBundles != null)
84  				nextScannedBundles.addAll(scannedBundles);
85  			nextScannedBundles.add(bundle);
86  			Bundle providerBundle = bundleWire.getProvider().getBundle();
87  			processBundle(providerBundle, nextScannedBundles);
88  		}
89  		for (BundleCapability bundleCapability : providedDataModels) {
90  			String name = (String) bundleCapability.getAttributes().get(DataModelNamespace.NAME);
91  			assert name != null;
92  			if (!dataModels.containsKey(name)) {
93  				DataModel dataModel = new DataModel(name, bundleCapability, requiredDataModels);
94  				dataModels.put(dataModel.getName(), dataModel);
95  			}
96  		}
97  	}
98  
99  	/** Return a negative depth if dataModel is required by ref, 0 otherwise. */
100 	static int required(DataModel ref, DataModel dataModel, int depth) {
101 		for (DataModel dm : ref.getRequired()) {
102 			if (dm.equals(dataModel))// found here
103 				return depth - 1;
104 			int d = required(dm, dataModel, depth - 1);
105 			if (d != 0)// found deeper
106 				return d;
107 		}
108 		return 0;// not found
109 	}
110 
111 	class DataModel {
112 		private final String name;
113 		private final boolean abstrct;
114 		// private final boolean standalone;
115 		private final String cnd;
116 		private final List<DataModel> required;
117 
118 		private DataModel(String name, BundleCapability bundleCapability, List<BundleWire> requiredDataModels) {
119 			assert CMS_DATA_MODEL_NAMESPACE.equals(bundleCapability.getNamespace());
120 			this.name = name;
121 			Map<String, Object> attrs = bundleCapability.getAttributes();
122 			abstrct = KernelUtils.asBoolean((String) attrs.get(DataModelNamespace.ABSTRACT));
123 			// standalone = KernelUtils.asBoolean((String)
124 			// attrs.get(DataModelNamespace.CAPABILITY_STANDALONE_ATTRIBUTE));
125 			cnd = (String) attrs.get(DataModelNamespace.CND);
126 			List<DataModel> req = new ArrayList<>();
127 			for (BundleWire wire : requiredDataModels) {
128 				String requiredDataModelName = (String) wire.getCapability().getAttributes()
129 						.get(DataModelNamespace.NAME);
130 				assert requiredDataModelName != null;
131 				DataModel requiredDataModel = dataModels.get(requiredDataModelName);
132 				if (requiredDataModel == null)
133 					throw new CmsException("No required data model " + requiredDataModelName);
134 				req.add(requiredDataModel);
135 			}
136 			required = Collections.unmodifiableList(req);
137 		}
138 
139 		public String getName() {
140 			return name;
141 		}
142 
143 		public boolean isAbstract() {
144 			return abstrct;
145 		}
146 
147 		// public boolean isStandalone() {
148 		// return !isAbstract();
149 		// }
150 
151 		public String getCnd() {
152 			return cnd;
153 		}
154 
155 		public List<DataModel> getRequired() {
156 			return required;
157 		}
158 
159 		// @Override
160 		// public int compareTo(DataModel o) {
161 		// if (equals(o))
162 		// return 0;
163 		// int res = required(this, o, 0);
164 		// if (res != 0)
165 		// return res;
166 		// // the other way round
167 		// res = required(o, this, 0);
168 		// if (res != 0)
169 		// return -res;
170 		// return 0;
171 		// }
172 
173 		@Override
174 		public int hashCode() {
175 			return name.hashCode();
176 		}
177 
178 		@Override
179 		public boolean equals(Object obj) {
180 			if (obj instanceof DataModel)
181 				return ((DataModel) obj).name.equals(name);
182 			return false;
183 		}
184 
185 		@Override
186 		public String toString() {
187 			return "Data model " + name;
188 		}
189 
190 	}
191 
192 }