/** @since 3.1 */ @Override public <T extends Persistent> T localObject(T objectFromAnotherContext) { if (objectFromAnotherContext == null) { throw new NullPointerException("Null object argument"); } ObjectId id = objectFromAnotherContext.getObjectId(); // first look for the ID in the local GraphManager synchronized (getGraphManager()) { T localObject = (T) getGraphManager().getNode(id); if (localObject != null) { return localObject; } // create a hollow object, optimistically assuming that the ID we // got from // 'objectFromAnotherContext' is a valid ID either in the parent // context or in // the DB. This essentially defers possible FaultFailureExceptions. ClassDescriptor descriptor = getEntityResolver().getClassDescriptor(id.getEntityName()); Persistent persistent = (Persistent) descriptor.createObject(); persistent.setObjectContext(this); persistent.setObjectId(id); persistent.setPersistenceState(PersistenceState.HOLLOW); getGraphManager().registerNode(id, persistent); return (T) persistent; } }
/** * Instantiates a new object and registers it with this context. Object class is determined from * the mapped entity. Object class must have a default constructor. * * <p><i>Note: in most cases {@link #newObject(Class)} method should be used, however this method * is helpful when generic persistent classes are used.</i> * * @since 3.0 */ public Persistent newObject(String entityName) { ClassDescriptor descriptor = getEntityResolver().getClassDescriptor(entityName); if (descriptor == null) { throw new IllegalArgumentException("Invalid entity name: " + entityName); } Persistent object; try { object = (Persistent) descriptor.createObject(); } catch (Exception ex) { throw new CayenneRuntimeException("Error instantiating object.", ex); } // this will initialize to-many lists descriptor.injectValueHolders(object); ObjectId id = new ObjectId(entityName); // note that the order of initialization of persistence artifacts below // is // important - do not change it lightly object.setObjectId(id); injectInitialValue(object); return object; }
@Override public void nodeRemoved(Object nodeId) { ObjectId id = (ObjectId) nodeId; final MutableObjectChange objectChangeSet = changeSet.getOrCreate(id, ObjectChangeType.DELETE); // TODO: rewrite with SelectById query after Cayenne upgrade ObjectIdQuery query = new ObjectIdQuery(id, true, ObjectIdQuery.CACHE); QueryResponse result = channel.onQuery(null, query); @SuppressWarnings("unchecked") List<DataRow> rows = result.firstList(); if (rows.isEmpty()) { LOGGER.warn( "No DB snapshot for object to be deleted, no changes will be recorded. ID: " + id); return; } final DataRow row = rows.get(0); ClassDescriptor descriptor = channel.getEntityResolver().getClassDescriptor(id.getEntityName()); final PostCommitEntity entity = entityFactory.getEntity(id); descriptor.visitProperties( new PropertyVisitor() { @Override public boolean visitAttribute(AttributeProperty property) { if (!entity.isIncluded(property.getName())) { return true; } Object value; if (entity.isConfidential(property.getName())) { value = Confidential.getInstance(); } else { String key = property.getAttribute().getDbAttributeName(); value = row.get(key); } if (value != null) { objectChangeSet.attributeChanged(property.getName(), value, null); } return true; } @Override public boolean visitToOne(ToOneProperty property) { // TODO record FK changes? return true; } @Override public boolean visitToMany(ToManyProperty property) { return true; } }); }
private PostCommitEntity createDescriptor(String entityName) { EntityResolver entityResolver = getEntityResolver(); ClassDescriptor classDescriptor = entityResolver.getClassDescriptor(entityName); Auditable annotation = classDescriptor.getObjectClass().getAnnotation(Auditable.class); if (annotation == null) { return BLOCKED_ENTITY; } ObjEntity entity = entityResolver.getObjEntity(entityName); return new DefaultPostCommitEntity( entity, annotation.ignoredProperties(), annotation.confidential()); }
/** * Returns a map key for a given to-many map relationship and a target object. * * @since 3.0 */ protected Object getMapKey(String relationshipName, Object value) { EntityResolver resolver = objectContext.getEntityResolver(); ClassDescriptor descriptor = resolver.getClassDescriptor(objectId.getEntityName()); if (descriptor == null) { throw new IllegalStateException("DataObject's entity is unmapped, objectId: " + objectId); } PropertyDescriptor property = descriptor.getProperty(relationshipName); if (property instanceof ToManyMapProperty) { return ((ToManyMapProperty) property).getMapKey(value); } throw new IllegalArgumentException( "Relationship '" + relationshipName + "' is not a to-many Map"); }
/** * An internal version of {@link #localObject(Object)} that operates on ObjectId instead of * Persistent, and wouldn't attempt to look up an object in the parent channel. * * @since 3.1 */ Persistent findOrCreateObject(ObjectId id) { if (id == null) { throw new IllegalArgumentException("Null ObjectId"); } // have to synchronize almost the entire method to prevent multiple // threads from // messing up dataobjects per CAY-845. Originally only parts of "else" // were // synchronized, but we had to expand the lock scope to ensure // consistent // behavior. synchronized (getGraphManager()) { Persistent cachedObject = (Persistent) getGraphManager().getNode(id); // return an existing object if (cachedObject != null) { int state = cachedObject.getPersistenceState(); // TODO: Andrus, 1/24/2006 implement smart merge for modified // objects... if (state != PersistenceState.MODIFIED && state != PersistenceState.DELETED) { ClassDescriptor descriptor = getEntityResolver().getClassDescriptor(id.getEntityName()); descriptor.injectValueHolders(cachedObject); } return cachedObject; } // create and register a hollow object ClassDescriptor descriptor = getEntityResolver().getClassDescriptor(id.getEntityName()); Persistent localObject = (Persistent) descriptor.createObject(); localObject.setObjectContext(this); localObject.setObjectId(id); getGraphManager().registerNode(id, localObject); localObject.setPersistenceState(PersistenceState.HOLLOW); return localObject; } }
/** * Registers a transient object with the context, recursively registering all transient persistent * objects attached to this object via relationships. * * <p><i>Note that since 3.0 this method takes Object as an argument instead of a {@link * DataObject}.</i> * * @param object new object that needs to be made persistent. */ @Override public void registerNewObject(Object object) { if (object == null) { throw new NullPointerException("Can't register null object."); } ObjEntity entity = getEntityResolver().getObjEntity((Persistent) object); if (entity == null) { throw new IllegalArgumentException( "Can't find ObjEntity for Persistent class: " + object.getClass().getName() + ", class is likely not mapped."); } final Persistent persistent = (Persistent) object; // sanity check - maybe already registered if (persistent.getObjectId() != null) { if (persistent.getObjectContext() == this) { // already registered, just ignore return; } else if (persistent.getObjectContext() != null) { throw new IllegalStateException( "Persistent is already registered with another DataContext. " + "Try using 'localObjects()' instead."); } } else { persistent.setObjectId(new ObjectId(entity.getName())); } ClassDescriptor descriptor = getEntityResolver().getClassDescriptor(entity.getName()); if (descriptor == null) { throw new IllegalArgumentException("Invalid entity name: " + entity.getName()); } injectInitialValue(object); // now we need to find all arc changes, inject missing value holders and // pull in // all transient connected objects descriptor.visitProperties( new PropertyVisitor() { public boolean visitToMany(ToManyProperty property) { property.injectValueHolder(persistent); if (!property.isFault(persistent)) { Object value = property.readProperty(persistent); Collection<Map.Entry> collection = (value instanceof Map) ? ((Map) value).entrySet() : (Collection) value; Iterator<Map.Entry> it = collection.iterator(); while (it.hasNext()) { Object target = it.next(); if (target instanceof Persistent) { Persistent targetDO = (Persistent) target; // make sure it is registered registerNewObject(targetDO); getObjectStore() .arcCreated( persistent.getObjectId(), targetDO.getObjectId(), property.getName()); } } } return true; } public boolean visitToOne(ToOneProperty property) { Object target = property.readPropertyDirectly(persistent); if (target instanceof Persistent) { Persistent targetDO = (Persistent) target; // make sure it is registered registerNewObject(targetDO); getObjectStore() .arcCreated(persistent.getObjectId(), targetDO.getObjectId(), property.getName()); } return true; } public boolean visitAttribute(AttributeProperty property) { return true; } }); }
/** * Returns a DataRow reflecting current, possibly uncommitted, object state. * * <p><strong>Warning:</strong> This method will return a partial snapshot if an object or one of * its related objects that propagate their keys to this object have temporary ids. DO NOT USE * this method if you expect a DataRow to represent a complete object state. * * @since 1.1 */ public DataRow currentSnapshot(final Persistent object) { // for a HOLLOW object return snapshot from cache if (object.getPersistenceState() == PersistenceState.HOLLOW && object.getObjectContext() != null) { return getObjectStore().getSnapshot(object.getObjectId()); } ObjEntity entity = getEntityResolver().getObjEntity(object); final ClassDescriptor descriptor = getEntityResolver().getClassDescriptor(entity.getName()); final DataRow snapshot = new DataRow(10); snapshot.setEntityName(entity.getName()); descriptor.visitProperties( new PropertyVisitor() { public boolean visitAttribute(AttributeProperty property) { ObjAttribute objAttr = property.getAttribute(); // processing compound attributes correctly snapshot.put(objAttr.getDbAttributePath(), property.readPropertyDirectly(object)); return true; } public boolean visitToMany(ToManyProperty property) { // do nothing return true; } public boolean visitToOne(ToOneProperty property) { ObjRelationship rel = property.getRelationship(); // if target doesn't propagates its key value, skip it if (rel.isSourceIndependentFromTargetChange()) { return true; } Object targetObject = property.readPropertyDirectly(object); if (targetObject == null) { return true; } // if target is Fault, get id attributes from stored snapshot // to avoid unneeded fault triggering if (targetObject instanceof Fault) { DataRow storedSnapshot = getObjectStore().getSnapshot(object.getObjectId()); if (storedSnapshot == null) { throw new CayenneRuntimeException( "No matching objects found for ObjectId " + object.getObjectId() + ". Object may have been deleted externally."); } DbRelationship dbRel = rel.getDbRelationships().get(0); for (DbJoin join : dbRel.getJoins()) { String key = join.getSourceName(); snapshot.put(key, storedSnapshot.get(key)); } return true; } // target is resolved and we have an FK->PK to it, // so extract it from target... Persistent target = (Persistent) targetObject; Map<String, Object> idParts = target.getObjectId().getIdSnapshot(); // this may happen in uncommitted objects - see the warning in // the JavaDoc // of // this method. if (idParts.isEmpty()) { return true; } DbRelationship dbRel = rel.getDbRelationships().get(0); Map<String, Object> fk = dbRel.srcFkSnapshotWithTargetSnapshot(idParts); snapshot.putAll(fk); return true; } }); // process object id map // we should ignore any object id values if a corresponding attribute // is a part of relationship "toMasterPK", since those values have been // set above when db relationships where processed. Map<String, Object> thisIdParts = object.getObjectId().getIdSnapshot(); if (thisIdParts != null) { // put only those that do not exist in the map for (Map.Entry<String, Object> entry : thisIdParts.entrySet()) { String nextKey = entry.getKey(); if (!snapshot.containsKey(nextKey)) { snapshot.put(nextKey, entry.getValue()); } } } return snapshot; }
@Override public void prepareForAccess(Persistent object, String property, boolean lazyFaulting) { if (object.getPersistenceState() == PersistenceState.HOLLOW) { ObjectId oid = object.getObjectId(); List<?> objects = performQuery(new ObjectIdQuery(oid, false, ObjectIdQuery.CACHE)); if (objects.size() == 0) { throw new FaultFailureException( "Error resolving fault, no matching row exists in the database for ObjectId: " + oid); } else if (objects.size() > 1) { throw new FaultFailureException( "Error resolving fault, more than one row exists in the database for ObjectId: " + oid); } // 5/28/2013 - Commented out this block to allow for modifying // objects in the postLoad callback // sanity check... // if (object.getPersistenceState() != PersistenceState.COMMITTED) { // // String state = // PersistenceState.persistenceStateName(object.getPersistenceState()); // // // TODO: andrus 4/13/2006, modified and deleted states are // // possible due to // // a race condition, should we handle them here? // throw new // FaultFailureException("Error resolving fault for ObjectId: " + // oid + " and state (" + state // + // "). Possible cause - matching row is missing from the database."); // } } // resolve relationship fault if (lazyFaulting && property != null) { ClassDescriptor classDescriptor = getEntityResolver().getClassDescriptor(object.getObjectId().getEntityName()); PropertyDescriptor propertyDescriptor = classDescriptor.getProperty(property); // If we don't have a property descriptor, there's not much we can // do. // Let the caller know that the specified property could not be // found and list // all of the properties that could be so the caller knows what can // be used. if (propertyDescriptor == null) { final StringBuilder errorMessage = new StringBuilder(); errorMessage.append( String.format( "Property '%s' is not declared for entity '%s'.", property, object.getObjectId().getEntityName())); errorMessage.append(" Declared properties are: "); // Grab each of the declared properties. final List<String> properties = new ArrayList<String>(); classDescriptor.visitProperties( new PropertyVisitor() { @Override public boolean visitAttribute(final AttributeProperty property) { properties.add(property.getName()); return true; } @Override public boolean visitToOne(final ToOneProperty property) { properties.add(property.getName()); return true; } @Override public boolean visitToMany(final ToManyProperty property) { properties.add(property.getName()); return true; } }); // Now add the declared property names to the error message. boolean first = true; for (String declaredProperty : properties) { if (first) { errorMessage.append(String.format("'%s'", declaredProperty)); first = false; } else { errorMessage.append(String.format(", '%s'", declaredProperty)); } } errorMessage.append("."); throw new CayenneRuntimeException(errorMessage.toString()); } // this should trigger fault resolving propertyDescriptor.readProperty(object); } }