001 /*
002 * Cumulus4j - Securing your data in the cloud - http://cumulus4j.org
003 * Copyright (C) 2011 NightLabs Consulting GmbH
004 *
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Affero General Public License as
007 * published by the Free Software Foundation, either version 3 of the
008 * License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013 * GNU Affero General Public License for more details.
014 *
015 * You should have received a copy of the GNU Affero General Public License
016 * along with this program. If not, see <http://www.gnu.org/licenses/>.
017 */
018 package org.cumulus4j.store;
019
020 import java.util.ArrayList;
021 import java.util.Collection;
022 import java.util.Collections;
023 import java.util.HashMap;
024 import java.util.HashSet;
025 import java.util.Map;
026 import java.util.Properties;
027 import java.util.Set;
028 import java.util.WeakHashMap;
029
030 import javax.jdo.FetchPlan;
031 import javax.jdo.PersistenceManager;
032
033 import org.cumulus4j.store.model.ClassMeta;
034 import org.cumulus4j.store.model.DataEntry;
035 import org.cumulus4j.store.model.FieldMeta;
036 import org.cumulus4j.store.model.FieldMetaRole;
037 import org.cumulus4j.store.model.IndexEntryFactoryRegistry;
038 import org.datanucleus.ClassLoaderResolver;
039 import org.datanucleus.NucleusContext;
040 import org.datanucleus.api.jdo.JDOPersistenceManagerFactory;
041 import org.datanucleus.identity.OID;
042 import org.datanucleus.identity.SCOID;
043 import org.datanucleus.metadata.AbstractClassMetaData;
044 import org.datanucleus.metadata.AbstractMemberMetaData;
045 import org.datanucleus.store.AbstractStoreManager;
046 import org.datanucleus.store.ExecutionContext;
047 import org.datanucleus.store.connection.ManagedConnection;
048 import org.datanucleus.store.schema.SchemaAwareStoreManager;
049 import org.slf4j.Logger;
050 import org.slf4j.LoggerFactory;
051
052 /**
053 * Store Manager for Cumulus4J operation.
054 * This StoreManager handles a backend StoreManager for the persistence to the chosen datastore, and optionally
055 * a second backend StoreManager for the persistence of index data to the chosen index datastore.
056 * The user will persist objects of their own classes, and these will be translated into the persistence of
057 * DataEntry, ClassMeta, FieldMeta for the data, as well as various IndexXXX types.
058 *
059 * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de
060 */
061 public class Cumulus4jStoreManager extends AbstractStoreManager implements SchemaAwareStoreManager
062 {
063 private static final Logger logger = LoggerFactory.getLogger(Cumulus4jStoreManager.class);
064
065 /** Extension key for marking field as not queryable */
066 public static final String CUMULUS4J_QUERYABLE = "cumulus4j-queryable";
067
068 // private static final SequenceMetaData SEQUENCE_META_DATA_DATA_ENTRY;
069 // static {
070 // SEQUENCE_META_DATA_DATA_ENTRY = new SequenceMetaData(DataEntry.class.getName(), SequenceStrategy.NONTRANSACTIONAL.toString());
071 // SEQUENCE_META_DATA_DATA_ENTRY.setAllocationSize(100);
072 // SEQUENCE_META_DATA_DATA_ENTRY.setDatastoreSequence(DataEntry.class.getName());
073 // }
074
075 private Map<Class<?>, ClassMeta> class2classMeta = Collections.synchronizedMap(new HashMap<Class<?>, ClassMeta>());
076
077 /**
078 * For every class, we keep a set of all known sub-classes (all inheritance-levels down). Note, that the class in
079 * the map-key is contained in the Set (in the map-value).
080 */
081 private Map<Class<?>, Set<Class<?>>> class2subclasses = Collections.synchronizedMap(new HashMap<Class<?>, Set<Class<?>>>());
082
083 private EncryptionHandler encryptionHandler;
084 private EncryptionCoordinateSetManager encryptionCoordinateSetManager;
085
086 private IndexEntryFactoryRegistry indexFactoryRegistry;
087
088 public Cumulus4jStoreManager(ClassLoaderResolver clr, NucleusContext nucleusContext, Map<String, Object> props)
089 {
090 super("cumulus4j", clr, nucleusContext, props);
091
092 logger.info("====================== Cumulus4j ======================");
093 String bundleName = "org.cumulus4j.store";
094 String version = nucleusContext.getPluginManager().getVersionForBundle(bundleName);
095 logger.info("Bundle: " + bundleName + " - Version: " + version);
096 logger.info("=======================================================");
097
098 indexFactoryRegistry = new IndexEntryFactoryRegistry(this);
099 encryptionHandler = new EncryptionHandler();
100 encryptionCoordinateSetManager = new EncryptionCoordinateSetManager();
101 persistenceHandler = new Cumulus4jPersistenceHandler(this);
102 }
103
104 public EncryptionHandler getEncryptionHandler() {
105 return encryptionHandler;
106 }
107
108 public EncryptionCoordinateSetManager getEncryptionCoordinateSetManager() {
109 return encryptionCoordinateSetManager;
110 }
111
112 public IndexEntryFactoryRegistry getIndexFactoryRegistry() {
113 return indexFactoryRegistry;
114 }
115
116 /**
117 * Get the persistent meta-data of a certain class. This persistent meta-data is primarily used for efficient
118 * mapping using long-identifiers instead of fully qualified class names.
119 *
120 * @param ec
121 * @param clazz the {@link Class} for which to query the meta-data.
122 * @return the meta-data. Never returns <code>null</code>.
123 */
124 public ClassMeta getClassMeta(ExecutionContext ec, Class<?> clazz)
125 {
126 ClassMeta result = class2classMeta.get(clazz);
127 if (result != null) {
128 return result;
129 }
130
131 ManagedConnection mconn = this.getConnection(ec);
132 try {
133 PersistenceManagerConnection pmConn = (PersistenceManagerConnection)mconn.getConnection();
134 PersistenceManager pm = pmConn.getDataPM();
135
136 synchronized (this) { // Synchronise in case we have data and index backends
137 // Register the class
138 pm.getFetchPlan().setGroup(FetchPlan.ALL);
139 result = registerClass(ec, pm, clazz);
140
141 // Detach the class in order to cache only detached objects. Make sure fetch-plan detaches all
142 pm.getFetchPlan().setGroup(FetchPlan.ALL);
143 pm.getFetchPlan().setMaxFetchDepth(-1);
144 result = pm.detachCopy(result);
145
146 if (pmConn.indexHasOwnPM()) {
147 // Replicate ClassMeta+FieldMeta to Index datastore
148 PersistenceManager pmIndex = pmConn.getIndexPM();
149 pmIndex.getFetchPlan().setGroup(FetchPlan.ALL);
150 pmIndex.getFetchPlan().setMaxFetchDepth(-1);
151 pmIndex.makePersistent(result);
152 }
153 }
154
155 class2classMeta.put(clazz, result);
156
157 // register in class2subclasses-map
158 Set<Class<?>> currentSubclasses = new HashSet<Class<?>>();
159 Class<?> c = clazz;
160 ClassMeta cm = result;
161 while (cm != null) {
162 currentSubclasses.add(c);
163
164 Set<Class<?>> subclasses;
165 synchronized (class2subclasses) {
166 subclasses = class2subclasses.get(c);
167 if (subclasses == null) {
168 subclasses = Collections.synchronizedSet(new HashSet<Class<?>>());
169 class2subclasses.put(c, subclasses);
170 }
171 }
172
173 subclasses.addAll(currentSubclasses);
174
175 c = c.getSuperclass();
176 cm = cm.getSuperClassMeta();
177 if (cm != null) {
178 if (c == null)
179 throw new IllegalStateException("c == null && cm.className == " + cm.getClassName());
180
181 if (!cm.getClassName().equals(c.getName()))
182 throw new IllegalStateException("cm.className != c.name :: cm.className=" + cm.getClassName() + " c.name=" + c.getName());
183
184 // Store the super-class-meta-data for optimisation reasons (not necessary, but [hopefully] better).
185 class2classMeta.put(c, cm);
186 }
187 }
188 } finally {
189 mconn.release();
190 }
191
192 return result;
193 }
194
195 private ClassMeta registerClass(ExecutionContext ec, PersistenceManager pm, Class<?> clazz)
196 {
197 AbstractClassMetaData dnClassMetaData = getMetaDataManager().getMetaDataForClass(clazz, ec.getClassLoaderResolver());
198 if (dnClassMetaData == null)
199 throw new IllegalArgumentException("The class " + clazz.getName() + " does not have persistence-meta-data! Is it persistence-capable? Is it enhanced?");
200
201 ClassMeta classMeta = ClassMeta.getClassMeta(pm, clazz, false);
202 boolean classExists = (classMeta != null);
203 if (!classExists) {
204 classMeta = new ClassMeta(clazz);
205 }
206
207 Class<?> superclass = clazz.getSuperclass();
208 if (superclass != null && getMetaDataManager().hasMetaDataForClass(superclass.getName())) {
209 ClassMeta superClassMeta = registerClass(ec, pm, superclass);
210 classMeta.setSuperClassMeta(superClassMeta);
211 }
212
213 Set<String> persistentMemberNames = new HashSet<String>();
214 for (AbstractMemberMetaData memberMetaData : dnClassMetaData.getManagedMembers()) {
215 if (!memberMetaData.isFieldToBePersisted())
216 continue;
217
218 persistentMemberNames.add(memberMetaData.getName());
219 int dnAbsoluteFieldNumber = memberMetaData.getAbsoluteFieldNumber();
220
221 // register primary field-meta
222 FieldMeta primaryFieldMeta = classMeta.getFieldMeta(memberMetaData.getName());
223 if (primaryFieldMeta == null) {
224 // adding field that's so far unknown
225 primaryFieldMeta = new FieldMeta(classMeta, memberMetaData.getName());
226 classMeta.addFieldMeta(primaryFieldMeta);
227 }
228 primaryFieldMeta.setDataNucleusAbsoluteFieldNumber(dnAbsoluteFieldNumber);
229
230 if (memberMetaData.hasCollection()) {
231 // register "collection" field-meta, if appropriate
232 primaryFieldMeta.removeAllSubFieldMetasExcept(FieldMetaRole.collectionElement);
233 FieldMeta subFieldMeta = primaryFieldMeta.getSubFieldMeta(FieldMetaRole.collectionElement);
234 if (subFieldMeta == null) {
235 // adding field that's so far unknown
236 subFieldMeta = new FieldMeta(primaryFieldMeta, FieldMetaRole.collectionElement);
237 primaryFieldMeta.addSubFieldMeta(subFieldMeta);
238 }
239 }
240 else if (memberMetaData.hasArray()) {
241 // register "array" field-meta, if appropriate
242 // TODO shouldn't we handle it exactly as a collection, including reusing 'FieldMetaRole.collectionElement' for this case?
243 primaryFieldMeta.removeAllSubFieldMetasExcept(FieldMetaRole.arrayElement);
244 FieldMeta subFieldMeta = primaryFieldMeta.getSubFieldMeta(FieldMetaRole.arrayElement);
245 if (subFieldMeta == null) {
246 // adding field that's so far unknown
247 subFieldMeta = new FieldMeta(primaryFieldMeta, FieldMetaRole.arrayElement);
248 primaryFieldMeta.addSubFieldMeta(subFieldMeta);
249 }
250 }
251 else if (memberMetaData.hasMap()) {
252 // register "map" field-meta, if appropriate
253 primaryFieldMeta.removeAllSubFieldMetasExcept(FieldMetaRole.mapKey, FieldMetaRole.mapValue);
254
255 FieldMeta subFieldMeta = primaryFieldMeta.getSubFieldMeta(FieldMetaRole.mapKey);
256 if (subFieldMeta == null) {
257 // adding field that's so far unknown
258 subFieldMeta = new FieldMeta(primaryFieldMeta, FieldMetaRole.mapKey);
259 primaryFieldMeta.addSubFieldMeta(subFieldMeta);
260 }
261
262 subFieldMeta = primaryFieldMeta.getSubFieldMeta(FieldMetaRole.mapValue);
263 if (subFieldMeta == null) {
264 // adding field that's so far unknown
265 subFieldMeta = new FieldMeta(primaryFieldMeta, FieldMetaRole.mapValue);
266 primaryFieldMeta.addSubFieldMeta(subFieldMeta);
267 }
268 }
269 else
270 primaryFieldMeta.removeAllSubFieldMetasExcept();
271 }
272
273 for (FieldMeta fieldMeta : new ArrayList<FieldMeta>(classMeta.getFieldMetas())) {
274 if (persistentMemberNames.contains(fieldMeta.getFieldName()))
275 continue;
276
277 // The field is not in the class anymore => remove its persistent reference.
278 classMeta.removeFieldMeta(fieldMeta);
279 }
280
281 if (!classExists) {
282 // Persist the new class and its fields in one call, minimising updates
283 pm.makePersistent(classMeta);
284 }
285 pm.flush(); // Get exceptions as soon as possible by forcing a flush here
286
287 return classMeta;
288 }
289
290 private Map<Object, String> objectID2className = Collections.synchronizedMap(new WeakHashMap<Object, String>());
291
292 /**
293 * Store the association between an objectID and the class-name of the corresponding persistable object in
294 * a {@link WeakHashMap}. This is used for performance optimization of
295 * {@link #getClassNameForObjectID(Object, ClassLoaderResolver, ExecutionContext)}.
296 */
297 public void setClassNameForObjectID(Object id, String className)
298 {
299 objectID2className.put(id, className);
300 }
301
302 @Override
303 public String getClassNameForObjectID(Object id, ClassLoaderResolver clr, ExecutionContext ec)
304 {
305 if (id == null) {
306 return null;
307 }
308
309 String className = objectID2className.get(id);
310 if (className != null) {
311 return className;
312 }
313
314 if (id instanceof SCOID) {
315 // Object is a SCOID
316 className = ((SCOID) id).getSCOClass();
317 }
318 else if (id instanceof OID) {
319 // Object is an OID
320 className = ((OID)id).getPcClass();
321 }
322 else if (getApiAdapter().isSingleFieldIdentity(id)) {
323 // Using SingleFieldIdentity so can assume that object is of the target class
324 className = getApiAdapter().getTargetClassNameForSingleFieldIdentity(id);
325 }
326 else {
327 // Application identity with user PK class, so find all using this PK
328 Collection<AbstractClassMetaData> cmds = getMetaDataManager().getClassMetaDataWithApplicationId(id.getClass().getName());
329 if (cmds != null) {
330 if (cmds.size() == 1) {
331 className = cmds.iterator().next().getFullClassName();
332 }
333 else {
334 ManagedConnection mconn = this.getConnection(ec);
335 try {
336 PersistenceManagerConnection pmConn = (PersistenceManagerConnection)mconn.getConnection();
337 PersistenceManager pmData = pmConn.getDataPM();
338 String objectIDString = id.toString();
339 for (AbstractClassMetaData cmd : cmds) {
340 Class<?> clazz = clr.classForName(cmd.getFullClassName());
341 ClassMeta classMeta = getClassMeta(ec, clazz);
342 DataEntry dataEntry = DataEntry.getDataEntry(pmData, classMeta, objectIDString);
343 if (dataEntry != null) {
344 className = cmd.getFullClassName();
345 }
346 }
347 } finally {
348 mconn.release();
349 }
350 }
351 }
352 }
353
354 if (className != null) {
355 objectID2className.put(id, className);
356 }
357
358 return className;
359 }
360
361 // public long nextDataEntryID(ExecutionContext executionContext)
362 // {
363 // NucleusSequence nucleusSequence = getNucleusSequence(executionContext, SEQUENCE_META_DATA_DATA_ENTRY);
364 // return nucleusSequence.nextValue();
365 // }
366
367 @Override
368 protected String getStrategyForNative(AbstractClassMetaData cmd, int absFieldNumber) {
369 return "increment";
370 // AbstractMemberMetaData mmd = cmd.getMetaDataForManagedMemberAtAbsolutePosition(absFieldNumber);
371 // if (String.class.isAssignableFrom(mmd.getType()) || UUID.class.isAssignableFrom(mmd.getType()))
372 // return "uuid-hex";
373 // else
374 // return "increment";
375 }
376
377 @Override
378 public void createSchema(Set<String> classNames, Properties props) {
379 Cumulus4jConnectionFactory cf =
380 (Cumulus4jConnectionFactory) connectionMgr.lookupConnectionFactory(txConnectionFactoryName);
381 JDOPersistenceManagerFactory pmfData = (JDOPersistenceManagerFactory) cf.getPMFData();
382 JDOPersistenceManagerFactory pmfIndex = (JDOPersistenceManagerFactory) cf.getPMFIndex();
383 if (pmfData.getNucleusContext().getStoreManager() instanceof SchemaAwareStoreManager) {
384 // Create Cumulus4J "Data" (plus "Index" if not separate) schema
385 SchemaAwareStoreManager schemaMgr = (SchemaAwareStoreManager) pmfData.getNucleusContext().getStoreManager();
386 Set<String> cumulus4jClassNames = new HashSet<String>();
387 Collection<Class> pmfClasses = pmfData.getManagedClasses();
388 for (Class cls : pmfClasses) {
389 cumulus4jClassNames.add(cls.getName());
390 }
391 schemaMgr.createSchema(cumulus4jClassNames, new Properties());
392 }
393 if (pmfIndex != null && pmfIndex.getNucleusContext().getStoreManager() instanceof SchemaAwareStoreManager) {
394 // Create Cumulus4J "Index" schema
395 SchemaAwareStoreManager schemaMgr = (SchemaAwareStoreManager) pmfIndex.getNucleusContext().getStoreManager();
396 Set<String> cumulus4jClassNames = new HashSet<String>();
397 Collection<Class> pmfClasses = pmfIndex.getManagedClasses();
398 for (Class cls : pmfClasses) {
399 cumulus4jClassNames.add(cls.getName());
400 }
401 schemaMgr.createSchema(cumulus4jClassNames, new Properties());
402 }
403 }
404
405 @Override
406 public void deleteSchema(Set<String> classNames, Properties props) {
407 Cumulus4jConnectionFactory cf =
408 (Cumulus4jConnectionFactory) connectionMgr.lookupConnectionFactory(txConnectionFactoryName);
409 JDOPersistenceManagerFactory pmfData = (JDOPersistenceManagerFactory) cf.getPMFData();
410 JDOPersistenceManagerFactory pmfIndex = (JDOPersistenceManagerFactory) cf.getPMFIndex();
411 if (pmfData.getNucleusContext().getStoreManager() instanceof SchemaAwareStoreManager) {
412 SchemaAwareStoreManager schemaMgr = (SchemaAwareStoreManager) pmfData.getNucleusContext().getStoreManager();
413 Set<String> cumulus4jClassNames = new HashSet<String>();
414 Collection<Class> pmfClasses = pmfData.getManagedClasses();
415 for (Class cls : pmfClasses) {
416 cumulus4jClassNames.add(cls.getName());
417 }
418 schemaMgr.deleteSchema(cumulus4jClassNames, new Properties());
419 }
420 if (pmfIndex != null && pmfIndex.getNucleusContext().getStoreManager() instanceof SchemaAwareStoreManager) {
421 SchemaAwareStoreManager schemaMgr = (SchemaAwareStoreManager) pmfIndex.getNucleusContext().getStoreManager();
422 Set<String> cumulus4jClassNames = new HashSet<String>();
423 Collection<Class> pmfClasses = pmfIndex.getManagedClasses();
424 for (Class cls : pmfClasses) {
425 cumulus4jClassNames.add(cls.getName());
426 }
427 schemaMgr.deleteSchema(cumulus4jClassNames, new Properties());
428 }
429 }
430
431 @Override
432 public void validateSchema(Set<String> classNames, Properties props) {
433 Cumulus4jConnectionFactory cf =
434 (Cumulus4jConnectionFactory) connectionMgr.lookupConnectionFactory(txConnectionFactoryName);
435 JDOPersistenceManagerFactory pmfData = (JDOPersistenceManagerFactory) cf.getPMFData();
436 JDOPersistenceManagerFactory pmfIndex = (JDOPersistenceManagerFactory) cf.getPMFIndex();
437 if (pmfData.getNucleusContext().getStoreManager() instanceof SchemaAwareStoreManager) {
438 SchemaAwareStoreManager schemaMgr = (SchemaAwareStoreManager) pmfData.getNucleusContext().getStoreManager();
439 Set<String> cumulus4jClassNames = new HashSet<String>();
440 Collection<Class> pmfClasses = pmfData.getManagedClasses();
441 for (Class cls : pmfClasses) {
442 cumulus4jClassNames.add(cls.getName());
443 }
444 schemaMgr.validateSchema(cumulus4jClassNames, new Properties());
445 }
446 if (pmfIndex != null && pmfIndex.getNucleusContext().getStoreManager() instanceof SchemaAwareStoreManager) {
447 SchemaAwareStoreManager schemaMgr = (SchemaAwareStoreManager) pmfIndex.getNucleusContext().getStoreManager();
448 Set<String> cumulus4jClassNames = new HashSet<String>();
449 Collection<Class> pmfClasses = pmfIndex.getManagedClasses();
450 for (Class cls : pmfClasses) {
451 cumulus4jClassNames.add(cls.getName());
452 }
453 schemaMgr.validateSchema(cumulus4jClassNames, new Properties());
454 }
455 }
456 }