[+BUGFIX] Extbase (Persistence): Fixed a problem where the cache was cleared at every...
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Persistence / Backend.php
index d28df9d..dec11f9 100644 (file)
@@ -5,7 +5,7 @@
 *  (c) 2009 Jochen Rau <jochen.rau@typoplanet.de>
 *  All rights reserved
 *
-*  This class is a backport of the corresponding class of FLOW3. 
+*  This class is a backport of the corresponding class of FLOW3.
 *  All credits go to the v5 team.
 *
 *  This script is part of the TYPO3 project. The TYPO3 project is
@@ -50,6 +50,16 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
         **/
        protected $identityMap;
 
+       /**
+        * @var Tx_Extbase_Reflection_Service
+        */
+       protected $reflectionService;
+
+       /**
+        * @var Tx_Extbase_Persistence_QueryFactoryInterface
+        */
+       protected $queryFactory;
+
        /**
         * @var Tx_Extbase_Persistence_QOM_QueryObjectModelFactoryInterface
         */
@@ -78,11 +88,10 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
        protected $referenceIndex;
 
        /**
-        * Storage page ID used to store records. Set by the Dispatcher.
-        * @var integer
-        */
-       protected $storagePageId;
-       
+        * @var array
+        **/
+       protected $extbaseSettings;
+
        /**
         * Constructs the backend
         *
@@ -91,11 +100,11 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
        public function __construct(Tx_Extbase_Persistence_Session $session, Tx_Extbase_Persistence_Storage_BackendInterface $storageBackend) {
                $this->session = $session;
                $this->storageBackend = $storageBackend;
-               $this->referenceIndex = t3lib_div::makeInstance('t3lib_refindex');
+               $this->extbaseSettings = Tx_Extbase_Dispatcher::getExtbaseFrameworkConfiguration();
+               if ($this->extbaseSettings['persistence']['updateReferenceIndex'] === '1') {
+                       $this->referenceIndex = t3lib_div::makeInstance('t3lib_refindex');
+               }
                $this->aggregateRootObjects = new Tx_Extbase_Persistence_ObjectStorage();
-               $this->persistenceBackend = $GLOBALS['TYPO3_DB']; // FIXME This is just an intermediate solution
-               $extbaseSettings = Tx_Extbase_Dispatcher::getSettings();
-               $this->storagePageId = $extbaseSettings['storagePid'];
        }
 
        /**
@@ -113,12 +122,31 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
         *
         * @param Tx_Extbase_Persistence_IdentityMap $identityMap
         * @return void
-        * @internal
         */
        public function injectIdentityMap(Tx_Extbase_Persistence_IdentityMap $identityMap) {
                $this->identityMap = $identityMap;
        }
 
+       /**
+        * Injects the Reflection Service
+        *
+        * @param Tx_Extbase_Reflection_Service
+        * @return void
+        */
+       public function injectReflectionService(Tx_Extbase_Reflection_Service $reflectionService) {
+               $this->reflectionService = $reflectionService;
+       }
+
+       /**
+        * Injects the QueryFactory
+        *
+        * @param Tx_Extbase_Persistence_QueryFactoryInterface $queryFactory
+        * @return void
+        */
+       public function injectQueryFactory(Tx_Extbase_Persistence_QueryFactoryInterface $queryFactory) {
+               $this->queryFactory = $queryFactory;
+       }
+       
        /**
         * Injects the QueryObjectModelFactory
         *
@@ -148,11 +176,19 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
                return $this->session;
        }
 
+       /**
+        * Returns the Data Mapper
+        *
+        * @return Tx_Extbase_Persistence_Mapper_DataMapper
+        */
+       public function getDataMapper() {
+               return $this->dataMapper;
+       }
+
        /**
         * Returns the current QOM factory
         *
         * @return Tx_Extbase_Persistence_QOM_QueryObjectModelFactoryInterface
-        * @internal
         */
        public function getQOMFactory() {
                return $this->QOMFactory;
@@ -162,7 +198,6 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
         * Returns the current value factory
         *
         * @return Tx_Extbase_Persistence_ValueFactoryInterface
-        * @internal
         */
        public function getValueFactory() {
                return $this->valueFactory;
@@ -172,7 +207,6 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
         * Returns the current identityMap
         *
         * @return Tx_Extbase_Persistence_IdentityMap
-        * @internal
         */
        public function getIdentityMap() {
                return $this->identityMap;
@@ -185,14 +219,36 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
         * @param object $object
         * @return string The identifier for the object if it is known, or NULL
         */
-       public function getUidByObject($object) {
+       public function getIdentifierByObject($object) {
                if ($this->identityMap->hasObject($object)) {
-                       return $this->identityMap->getUidByObject($object);
+                       return $this->identityMap->getIdentifierByObject($object);
                } else {
                        return NULL;
                }
        }
 
+       /**
+        * Returns the object with the (internal) identifier, if it is known to the
+        * backend. Otherwise NULL is returned.
+        *
+        * @param string $identifier
+        * @param string $className
+        * @return object The object for the identifier if it is known, or NULL
+        */
+       public function getObjectByIdentifier($identifier, $className) {
+               if ($this->identityMap->hasIdentifier($identifier, $className)) {
+                       return $this->identityMap->getObjectByIdentifier($identifier, $className);
+               } else {
+                       $query = $this->queryFactory->create($className);
+                       $result = $query->matching($query->withUid($identifier))->execute();
+                       $object = NULL;
+                       if (count($result) > 0) {
+                               $object = current($result);
+                       }
+                       return $object;
+               }
+       }
+
        /**
         * Checks if the given object has ever been persisted.
         *
@@ -200,7 +256,7 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
         * @return boolean TRUE if the object is new, FALSE if the object exists in the repository
         */
        public function isNewObject($object) {
-               return ($this->getUidByObject($object) === NULL);
+               return ($this->getIdentifierByObject($object) === NULL);
        }
 
        /**
@@ -219,7 +275,7 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
         * @return void
         */
        public function replaceObject($existingObject, $newObject) {
-               $existingUid = $this->getUidByObject($existingObject);
+               $existingUid = $this->getIdentifierByObject($existingObject);
                if ($existingUid === NULL) throw new Tx_Extbase_Persistence_Exception_UnknownObject('The given object is unknown to this persistence backend.', 1238070163);
 
                $this->identityMap->unregisterObject($existingObject);
@@ -262,173 +318,363 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
         * @return void
         */
        protected function persistObjects() {
+               foreach ($this->aggregateRootObjects as $object) {
+                       // if (!$this->identityMap->hasObject($object)) { // TODO Must be enabled to allow other identity properties than $uid
+                       if ($object->_isNew()) {
+                               $this->insertObject($object);
+                       }
+               }
+               
                foreach ($this->aggregateRootObjects as $object) {
                        $this->persistObject($object);
                }
        }
 
        /**
-        * Inserts an objects corresponding row into the database. If the object is a value object an
-        * existing instance will be looked up.
+        * Persists an object (instert, update) and its related objects (instert, update, delete).
         *
         * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to be inserted
         * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject The parent object
         * @param string $parentPropertyName The name of the property the object is stored in
         * @return void
         */
-       protected function persistObject($object, $parentObject = NULL, $parentPropertyName = NULL, $processQueue = TRUE) {
+       protected function persistObject(Tx_Extbase_DomainObject_DomainObjectInterface $object) {
                $row = array();
                $queue = array();
                $className = get_class($object);
                $dataMap = $this->dataMapper->getDataMap($className);
+               $classSchema = $this->reflectionService->getClassSchema($className);
+               
                $properties = $object->_getProperties();
-
-               if ($object instanceof Tx_Extbase_DomainObject_AbstractValueObject) {
-                       $this->checkForAlreadyPersistedValueObject($object);
-               }
-
-               // Fill up $row[$columnName] array with changed values which need to be stored
                foreach ($properties as $propertyName => $propertyValue) {
-                       if (!$dataMap->isPersistableProperty($propertyName) || ($propertyValue instanceof Tx_Extbase_Persistence_LazyLoadingProxy)) {
+                       if (!$dataMap->isPersistableProperty($propertyName)) continue;
+                       if (($propertyValue instanceof Tx_Extbase_Persistence_LazyLoadingProxy) || ((get_class($propertyValue) === 'Tx_Extbase_Persistence_LazyObjectStorage') && ($propertyValue->isInitialized() === FALSE))) {
                                continue;
                        }
-
+                       
                        $columnMap = $dataMap->getColumnMap($propertyName);
-                       $columnName = $columnMap->getColumnName();
-                       if ($object->_isNew() || $object->_isDirty($propertyName)) {
-                               if ($columnMap->isRelation()) {
-                                       if (($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) || ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY)) {
-                                               $row[$columnName] = count($properties[$propertyName]);
-                                               foreach ($propertyValue as $containedObject) {
-                                                       $queue[] = array($propertyName => $containedObject);
-                                               }
-                                       } elseif ($propertyValue instanceof Tx_Extbase_DomainObject_DomainObjectInterface) {
-                                               // TODO Handle Value Objects different
-                                               // SK: this is the case RELATION_HAS_ONE, correct?
-                                               if ($propertyValue->_isNew() || $propertyValue->_isDirty()) {
-                                                       // SK: What happens if the value is not new, but changed?
-                                                       $this->persistObject($propertyValue);
+                       $propertyMetaData = $classSchema->getProperty($propertyName);
+                       $propertyType = $propertyMetaData['type'];
+                       // FIXME enable property-type check
+                       // $this->checkPropertyType($propertyType, $propertyValue);
+                       if (($propertyValue !== NULL) && $propertyType === 'Tx_Extbase_Persistence_ObjectStorage') {
+                               if ($object->_isNew() || $object->_isDirty($propertyName)) {
+                                       $this->persistObjectStorage($propertyValue, $object, $propertyName, $queue, $row);
+                               } else {
+                                       foreach ($propertyValue as $containedObject) {
+                                               $queue[] = $containedObject;
+                                       }
+                               }
+                       } elseif ($propertyValue instanceof Tx_Extbase_DomainObject_DomainObjectInterface) {                            
+                               if ($object->_isDirty($propertyName)) {
+                                       if ($propertyValue->_isNew()) {
+                                               if ($propertyValue instanceof Tx_Extbase_DomainObject_AbstractEntity) {
+                                                       $this->insertObject($propertyValue, $object, $propertyName);
+                                                       $queue[] = $propertyValue;
+                                               } else {
+                                                       $this->persistValueObject($propertyValue, $object, $propertyName);
                                                }
-                                               $row[$columnName] = $propertyValue->getUid();
                                        }
-                               } else {
-                                       // Not an relation, this means it is a simple type such as STRING or Integer
-                                       $row[$columnName] = $dataMap->convertPropertyValueToFieldValue($properties[$propertyName]);
+                                       $row[$columnMap->getColumnName()] = $dataMap->convertPropertyValueToFieldValue($propertyValue);
                                }
+                       } elseif ($object instanceof Tx_Extbase_DomainObject_AbstractValueObject || ($object->_isNew() || $object->_isDirty($propertyName))) {
+                               $row[$columnMap->getColumnName()] = $dataMap->convertPropertyValueToFieldValue($propertyValue);
                        }
-               } // end property iteration for loop
-
-               if ($object->_isNew()) {
-                       $this->insertObject($object, $parentObject, $parentPropertyName, $row);
-               } elseif ($object->_isDirty()) {
-                       $this->updateObject($object, $parentObject, $parentPropertyName, $row);
                }
                
-               // SK: I need to check the code below more thoroughly
-               if ($parentObject instanceof Tx_Extbase_DomainObject_DomainObjectInterface && !empty($parentPropertyName)) {
-                       $parentClassName = get_class($parentObject);
-                       $parentDataMap = $this->dataMapper->getDataMap($parentClassName);
-                       $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
-
-                       if (($parentColumnMap->getTypeOfRelation()  === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY)) {
-                               $this->insertRelation($object, $parentObject, $parentPropertyName);
-                       }
+               if (count($row) > 0) {
+                       $this->updateObject($object, $row);
                }
-
+               
                if ($object instanceof Tx_Extbase_DomainObject_AbstractEntity) {
                        $object->_memorizeCleanState();
                }
+
+               foreach ($queue as $object) {
+                       $this->persistObject($object);
+               }
                
-               // SK: Where does $queue come from? Do we need the code below?
-               if ($processQueue === TRUE) {
-                       foreach ($queue as $queuedObjects) {
-                               foreach($queuedObjects as $propertyName => $queuedObject) {
-                                       $this->persistObject($queuedObject, $object, $propertyName);
-                               }
-                       }
+       }
+
+       /**
+        * Checks a value given against the expected type. If not matching, an
+        * UnexpectedTypeException is thrown. NULL is always considered valid.
+        *
+        * @param string $expectedType The expected type
+        * @param mixed $value The value to check
+        * @return void
+        * @throws Tx_Extbase_Persistence_Exception_UnexpectedType
+        */
+       protected function checkPropertyType($expectedType, $value) {
+               if ($value === NULL) {
+                       return;
                }
 
+               if (is_object($value)) {
+                       if (!($value instanceof $expectedType)) {
+                               throw new Tx_Extbase_Persistence_Exception_UnexpectedTypeException('Expected property of type ' . $expectedType . ', but got ' . get_class($value), 1244465558);                                
+                       }
+               } elseif ($expectedType !== gettype($value)) {                  
+                       throw new Tx_Extbase_Persistence_Exception_UnexpectedTypeException('Expected property of type ' . $expectedType . ', but got ' . gettype($value), 1244465558);
+               }
        }
+       
+       /**
+        * Persists the given value object.
+        *
+        * @return void
+        */
+       protected function persistValueObject(Tx_Extbase_DomainObject_AbstractValueObject $object, Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $parentPropertyName, $sortingPosition = 1) {
+               $result = $this->getUidOfAlreadyPersistedValueObject($object);
+               if ($result !== FALSE) {
+                       $object->_setProperty('uid', (int)$result);
+                       if($this->dataMapper->getDataMap(get_class($parentObject))->getColumnMap($parentPropertyName)->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
+                               $this->insertRelationInRelationtable($object, $parentObject, $parentPropertyName, $sortingPosition);
+                       }
+               } elseif ($object->_isNew()) {
+                       $row = array();
+                       $className = get_class($object);
+                       $dataMap = $this->dataMapper->getDataMap($className);
+                       $classSchema = $this->reflectionService->getClassSchema($className);
+
+                       $properties = $object->_getProperties();
+                       foreach ($properties as $propertyName => $propertyValue) {
+                               if (!$dataMap->isPersistableProperty($propertyName)) continue;
+                               if (($propertyValue instanceof Tx_Extbase_Persistence_LazyLoadingProxy) || ((get_class($propertyValue) === 'Tx_Extbase_Persistence_LazyObjectStorage') && ($propertyValue->isInitialized() === FALSE))) {
+                                       continue;
+                               }
 
-       /*
-        * Tests, if the given Domain Object already exists in the storage backend
+                               $columnMap = $dataMap->getColumnMap($propertyName);
+                               $propertyMetaData = $classSchema->getProperty($propertyName);
+                               $propertyType = $propertyMetaData['type'];
+                               // FIXME enable property-type check
+                               // $this->checkPropertyType($propertyType, $propertyValue);
+                               $row[$columnMap->getColumnName()] = $dataMap->convertPropertyValueToFieldValue($propertyValue);
+                       }
+                       $this->insertObject($object, $parentObject, $parentPropertyName, $sortingPosition, $row);
+               }
+       }
+       
+       /**
+        * Tests, if the given Value Object already exists in the storage backend and if so, it returns the uid.
         *
         * @param Tx_Extbase_DomainObject_AbstractValueObject $object The object to be tested
         */
-       protected function checkForAlreadyPersistedValueObject(Tx_Extbase_DomainObject_AbstractValueObject $object) {
-               $dataMap = $this->dataMapper->getDataMap(get_class($object));
-               $properties = $object->_getProperties();
-               $result = $this->storageBackend->hasValueObject($properties, $dataMap);
-               if ($result !== FALSE) {
-                       $object->_setProperty('uid', $result);
+       protected function getUidOfAlreadyPersistedValueObject(Tx_Extbase_DomainObject_AbstractValueObject $object) {
+               return $this->storageBackend->getUidOfAlreadyPersistedValueObject($object);
+       }
+       
+       /**
+        * Persists a relation. Objects of a 1:n or m:n relation are queued and processed with the parent object. A 1:1 relation
+        * gets persisted immediately. Objects which were removed from the property were detached from the parent object. They will not be
+        * deleted by default. You have to annotate the property with "@cascade remove" if you want them to be deleted as well.
+        *
+        * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object
+        * @param string $propertyName The name of the property the related objects are stored in
+        * @param mixed $propertyValue The property value 
+        * @return void
+        */
+       protected function persistObjectStorage(Tx_Extbase_Persistence_ObjectStorage $objectStorage, Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $propertyName, array &$queue, array &$row) {
+               $className = get_class($parentObject);
+               $columnMap = $this->dataMapper->getDataMap($className)->getColumnMap($propertyName);
+               $columnName = $columnMap->getColumnName();              
+               $propertyMetaData = $this->reflectionService->getClassSchema($className)->getProperty($propertyName);
+
+               $updateParent = FALSE;          
+               foreach ($this->getRemovedChildObjects($parentObject, $propertyName) as $removedObject) {
+                       if ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY && $propertyMetaData['cascade'] === 'remove') {
+                               $this->removeObject($removedObject);
+                       } else {
+                               $this->detachObjectFromParentObject($removedObject, $parentObject, $propertyName);
+                       }
+                       $updateParent = TRUE;
+               }
+               
+               $sortingPosition = 1;
+               foreach ($objectStorage as $object) {
+                       if ($object->_isNew()) {
+                               if ($object instanceof Tx_Extbase_DomainObject_AbstractEntity) {
+                                       $this->insertObject($object, $parentObject, $propertyName, $sortingPosition);
+                                       $queue[] = $object;
+                               } else {
+                                       $this->persistValueObject($object, $parentObject, $propertyName, $sortingPosition);
+                               }
+                               $updateParent = TRUE;
+                       }
+                       $sortingPosition++;
+               }
+               
+               if ($updateParent === TRUE && $columnMap->getParentKeyFieldName() !== NULL) {
+                       $row[$columnMap->getColumnName()] = $this->dataMapper->countRelated($parentObject, $propertyName);                      
                }
        }
-
+       
+       /**
+        * Returns the removed objects determined by a comparison of the clean property value
+        * with the actual property value.
+        *
+        * @param Tx_Extbase_DomainObject_AbstractEntity $object The object
+        * @param string $parentPropertyName The name of the property
+        * @return array An array of removed objects
+        */
+       protected function getRemovedChildObjects(Tx_Extbase_DomainObject_AbstractEntity $object, $propertyName) {
+               $removedObjects = array();
+               $cleanPropertyValue = $object->_getCleanProperty($propertyName);
+               $propertyValue = $object->_getProperty($propertyName);
+               if ($cleanPropertyValue instanceof Tx_Extbase_Persistence_ObjectStorage) {
+                       $cleanPropertyValue = $cleanPropertyValue->toArray();
+               }
+               if ($propertyValue instanceof Tx_Extbase_Persistence_ObjectStorage) {
+                       $propertyValue = $propertyValue->toArray();
+               }
+               if ($cleanPropertyValue instanceof Iterator) {
+                       foreach ($cleanPropertyValue as $hash => $item) {
+                               if (!array_key_exists($hash, $propertyValue)) {
+                                       $removedObjects[] = $item;
+                               }
+                       }
+               }
+               return $removedObjects;
+       }
+       
+       /**
+        * Updates the fields defining the relation between the object and the parent object.
+        *
+        * @param Tx_Extbase_DomainObject_DomainObjectInterface $object 
+        * @param Tx_Extbase_DomainObject_AbstractEntity $parentObject 
+        * @param string $parentPropertyName 
+        * @return void
+        */
+       protected function detachObjectFromParentObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, Tx_Extbase_DomainObject_AbstractEntity $parentObject, $parentPropertyName) {
+               $parentDataMap = $this->dataMapper->getDataMap(get_class($parentObject));
+               $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
+               if ($parentColumnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) {
+                       $row = array();
+                       $parentKeyFieldName = $parentColumnMap->getParentKeyFieldName();
+                       if ($parentKeyFieldName !== NULL) {
+                               $row[$parentKeyFieldName] = '';
+                               $parentTableFieldName = $parentColumnMap->getParentTableFieldName();
+                               if ($parentTableFieldName !== NULL) {
+                                       $row[$parentTableFieldName] = '';
+                               }
+                       }
+                       if (count($row) > 0) {
+                               $this->updateObject($object, $row);
+                       }
+               } elseif ($parentColumnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
+                       $this->deleteRelationFromRelationtable($object, $parentObject, $parentPropertyName);
+               }               
+       }
+       
        /**
         * Inserts an object in the storage
-        * 
+        *
         * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to be insterted in the storage
-        * @param Tx_Extbase_DomainObject_AbstractEntity|NULL $parentObject The parent object (if any)
-        * @param string|NULL $parentPropertyName The name of the property
-        * @param array $row The $row
-        */
-       protected function insertObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, Tx_Extbase_DomainObject_AbstractEntity $parentObject = NULL, $parentPropertyName = NULL, array &$row) {
-               $className = get_class($object);
-               $dataMap = $this->dataMapper->getDataMap($className);
-               $tableName = $dataMap->getTableName();
-               $this->addCommonFieldsToRow($object, $parentObject, $parentPropertyName, $row);
-               $uid = $this->storageBackend->addRow(
-                       $tableName,
-                       $row
-                       );
-               $object->_setProperty('uid', $uid);
-               $this->referenceIndex->updateRefIndexTable($tableName, $uid);
+        * @param array $row The tuple to be inserted
+        * @param Tx_Extbase_DomainObject_AbstractEntity $parentObject The parent object (if any)
+        * @param string $parentPropertyName The name of the property
+        */
+       protected function insertObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, Tx_Extbase_DomainObject_AbstractEntity $parentObject = NULL, $propertyName = NULL, $sortingPosition = NULL, array $row = array()) {
+               $tableName = $this->dataMapper->getDataMap(get_class($object))->getTableName();
+               $this->addCommonFieldsToRow($object, $row);
+               if ($parentObject !== NULL) {
+                       $parentColumnMap = $this->dataMapper->getDataMap(get_class($parentObject))->getColumnMap($propertyName);
+                       if ($parentColumnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY && $parentColumnMap->getParentKeyFieldName() !== NULL) {
+                               $row[$parentColumnMap->getParentKeyFieldName()] = $parentObject->getUid();
+                       }
+               }
+               if ($object->_isNew()) {
+                       $uid = $this->storageBackend->addRow(
+                               $tableName,
+                               $row
+                               );
+                       $object->_setProperty('uid', (int)$uid);
+               }
+               if ($parentObject !== NULL) {
+                       if($parentColumnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
+                               $this->insertRelationInRelationtable($object, $parentObject, $propertyName, $sortingPosition);
+                       }
+               }
+               if ($this->extbaseSettings['persistence']['updateReferenceIndex'] === '1') {
+                       $this->referenceIndex->updateRefIndexTable($tableName, $uid);
+               }
+               $this->identityMap->registerObject($object, $uid);
        }
-
+       
        /**
         * Inserts mm-relation into a relation table
         *
-        * @param Tx_Extbase_DomainObject_DomainObjectInterface $relatedObject The related object
+        * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The related object
         * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject The parent object
-        * @param string $parentPropertyName The name of the parent object's property where the related objects are stored in
+        * @param string $propertyName The name of the parent object's property where the related objects are stored in
         * @return void
         */
-       protected function insertRelation(Tx_Extbase_DomainObject_DomainObjectInterface $relatedObject, Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $parentPropertyName) {
+       protected function insertRelationInRelationtable(Tx_Extbase_DomainObject_DomainObjectInterface $object, Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $propertyName, $sortingPosition = NULL) {
                $dataMap = $this->dataMapper->getDataMap(get_class($parentObject));
+               $columnMap = $dataMap->getColumnMap($propertyName);
                $row = array(
-                       'uid_local' => (int)$parentObject->getUid(), // TODO Aliases for relation field names
-                       'uid_foreign' => (int)$relatedObject->getUid(),
-                       'tablenames' => $dataMap->getTableName(),
-                       'sorting' => 9999 // TODO sorting of mm table items
+                       $columnMap->getParentKeyFieldName() => (int)$parentObject->getUid(),
+                       $columnMap->getChildKeyFieldName() => (int)$object->getUid(),
+                       $columnMap->getChildSortByFieldName() => !is_null($sortingPosition) ? (int)$sortingPosition : 0
                        );
-               $tableName = $dataMap->getColumnMap($parentPropertyName)->getRelationTableName();
+               $relationTableName = $columnMap->getRelationTableName();
+               // FIXME Reenable support for tablenames
+               // $childTableName = $columnMap->getChildTableName();
+               // if (isset($childTableName)) {
+               //      $row['tablenames'] = $childTableName;
+               // }
                $res = $this->storageBackend->addRow(
-                       $tableName,
+                       $relationTableName,
                        $row,
                        TRUE);
                return $res;
        }
 
+       /**
+        * Delete an mm-relation from a relation table
+        *
+        * @param Tx_Extbase_DomainObject_DomainObjectInterface $relatedObject The related object
+        * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject The parent object
+        * @param string $parentPropertyName The name of the parent object's property where the related objects are stored in
+        * @return void
+        */
+       protected function deleteRelationFromRelationtable(Tx_Extbase_DomainObject_DomainObjectInterface $relatedObject, Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $parentPropertyName) {
+               $dataMap = $this->dataMapper->getDataMap(get_class($parentObject));
+               $columnMap = $dataMap->getColumnMap($parentPropertyName);
+               $relationTableName = $columnMap->getRelationTableName();
+               $res = $this->storageBackend->removeRow(
+                       $relationTableName,
+                       array(
+                               $columnMap->getParentKeyFieldName() => (int)$parentObject->getUid(),
+                               $columnMap->getChildKeyFieldName() => (int)$relatedObject->getUid(),
+                               ),
+                       FALSE);
+               return $res;
+       }
+
        /**
         * Updates a given object in the storage
-        * 
+        *
         * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to be insterted in the storage
         * @param Tx_Extbase_DomainObject_AbstractEntity|NULL $parentObject The parent object (if any)
         * @param string|NULL $parentPropertyName The name of the property
         * @param array $row The $row
         */
-       protected function updateObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, $parentObject = NULL, $parentPropertyName = NULL, array &$row) {
-               $className = get_class($object);
-               $dataMap = $this->dataMapper->getDataMap($className);
-               $tableName = $dataMap->getTableName();
-               $this->addCommonFieldsToRow($object, $parentObject, $parentPropertyName, $row);
+       protected function updateObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, array &$row) {
+               $tableName = $this->dataMapper->getDataMap(get_class($object))->getTableName();
+               $this->addCommonFieldsToRow($object, $row);
                $uid = $object->getUid();
                $row['uid'] = $uid;
                $res = $this->storageBackend->updateRow(
                        $tableName,
                        $row
                        );
-               $this->referenceIndex->updateRefIndexTable($tableName, $uid);
+               if ($this->extbaseSettings['persistence']['updateReferenceIndex'] === '1') {
+                       $this->referenceIndex->updateRefIndexTable($tableName, $uid);
+               }
+               if ($object instanceof Tx_Extbase_DomainObject_AbstractEntity) {
+                       $object->_memorizeCleanState();
+               }       
                return $res;
        }
 
@@ -439,8 +685,7 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
         * @param array $properties The properties of the object
         * @return array A single row to be inserted in the database
         */
-       // TODO Should we pass an extra flag (UPDATE/INSERT)?
-       protected function addCommonFieldsToRow(Tx_Extbase_DomainObject_DomainObjectInterface $object, $parentObject = NULL, $parentPropertyName = NULL, array &$row) {
+       protected function addCommonFieldsToRow(Tx_Extbase_DomainObject_DomainObjectInterface $object, array &$row) {
                $className = get_class($object);
                $dataMap = $this->dataMapper->getDataMap($className);
                if ($dataMap->hasCreationDateColumn() && $object->_isNew()) {
@@ -449,81 +694,37 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
                if ($dataMap->hasTimestampColumn()) {
                        $row[$dataMap->getTimestampColumnName()] = $GLOBALS['EXEC_TIME'];
                }
-               if ($dataMap->hasPidColumn() && !isset($row['pid'])) {
-                       $row['pid'] = $this->storagePageId;
-               }
-               if ($parentObject instanceof Tx_Extbase_DomainObject_DomainObjectInterface && !empty($parentPropertyName)) {
-                       $parentDataMap = $this->dataMapper->getDataMap(get_class($parentObject));
-                       $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
-                       $parentKeyFieldName = $parentColumnMap->getParentKeyFieldName();
-                       if ($parentKeyFieldName !== NULL) {
-                               $row[$parentKeyFieldName] = $parentObject->getUid();
-                       }
-                       $parentTableFieldName = $parentColumnMap->getParentTableFieldName();
-                       if ($parentTableFieldName !== NULL) {
-                               $row[$parentTableFieldName] = $parentDataMap->getTableName();
-                       }
+               if ($object->_isNew() && $dataMap->hasPidColumn() && !isset($row['pid'])) {
+                       $row['pid'] = $this->determineStoragePageIdForNewRecord($object);
                }
        }
 
        /**
-        * Inserts and updates all relations of an object. It also inserts and updates data in relation tables.
-        *
-        * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object for which the relations should be updated
-        * @param string $propertyName The name of the property holding the related child objects
-        * @param array $relations The queued relations
-        * @return void
-        */
-       protected function persistRelations(Tx_Extbase_DomainObject_DomainObjectInterface $object, $propertyName, array $relations) {
-               $dataMap = $this->dataMapper->getDataMap(get_class($object));
-               foreach ($relations as $propertyName => $relatedObjects) {
-                       if (!empty($relatedObjects)) {
-                               $typeOfRelation = $dataMap->getColumnMap($propertyName)->getTypeOfRelation();
-                               foreach ($relatedObjects as $relatedObject) {
-                                       if ($relatedObject->_isNew()) {
-                                               $this->persistObject($relatedObject, $object, $propertyName);
-                                               if ($typeOfRelation === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
-                                                       $this->insertRelationInRelationTable($relatedObject, $object, $propertyName);
-                                               }
-                                       } elseif ($relatedObject->_isDirty()) {
-                                               $this->persistObject($relatedObject, $object, $propertyName);
-                                       }
-                               }
-                       }
-               }
-       }
-
-       /**
-        * Iterate over deleted objects and process them
+        * Iterate over deleted aggregate root objects and process them
         *
         * @return void
         */
        protected function processDeletedObjects() {
                foreach ($this->deletedObjects as $object) {
-                       $this->deleteObject($object);
-                       if ($this->identityMap->hasObject($object)) {
-                               $this->session->registerRemovedObject($object);
-                               $this->identityMap->unregisterObject($object);
-                       }
+                       $this->removeObject($object);
+                       $this->identityMap->unregisterObject($object);
                }
                $this->deletedObjects = new Tx_Extbase_Persistence_ObjectStorage();
        }
 
        /**
-        * Deletes an object, it's 1:n related objects, and the m:n relations in relation tables (but not the m:n related objects!)
+        * Deletes an object
         *
         * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to be insterted in the storage
         * @param Tx_Extbase_DomainObject_AbstractEntity|NULL $parentObject The parent object (if any)
         * @param string|NULL $parentPropertyName The name of the property
         * @param bool $markAsDeleted Shold we only mark the row as deleted instead of deleting (TRUE by default)?
-        * @param bool $recurseIntoRelations Shold we delete also dependant aggregates (FALSE by default)?
         * @return void
         */
-       protected function deleteObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, $parentObject = NULL, $parentPropertyName = NULL, $markAsDeleted = TRUE, $recurseIntoRelations = FALSE) {
-               // TODO Implement recursive deletions
+       protected function removeObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, $markAsDeleted = TRUE) {
                $dataMap = $this->dataMapper->getDataMap(get_class($object));
                $tableName = $dataMap->getTableName();
-               if ($markAsDeleted === TRUE && $dataMap->hasDeletedColumn()) {
+               if (($markAsDeleted === TRUE) && $dataMap->hasDeletedColumn()) {
                        $deletedColumnName = $dataMap->getDeletedColumnName();
                        $res = $this->storageBackend->updateRow(
                                $tableName,
@@ -535,77 +736,42 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
                } else {
                        $res = $this->storageBackend->removeRow(
                                $tableName,
-                               $object->getUid()
+                               array('uid' => $object->getUid())
                                );
                }
-               $this->referenceIndex->updateRefIndexTable($tableName, $uid);
+               $this->removeRelatedObjects($object);
+               if ($this->extbaseSettings['persistence']['updateReferenceIndex'] === '1') {
+                       $this->referenceIndex->updateRefIndexTable($tableName, $object->getUid());
+               }               
        }
-
+       
        /**
-        * Deletes all relations of an object.
+        * Remove related objects
         *
-        * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object for which the relations should be updated
-        * @param string $propertyName The name of the property holding the related child objects
-        * @param array $relations The queued relations
+        * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to scanned for related objects
         * @return void
         */
-       // SK: The below method is never called.
-       // SK: I think there is still a problem with deleted objects and deleted relations.
-       // SK: I am not yet sure where deleted relations ae handled. Need to check more thoroughly!
-       protected function deleteRelatedObjects(Tx_Extbase_DomainObject_DomainObjectInterface $object, array $relations) {
-               $dataMap = $this->dataMapper->getDataMap(get_class($object));
-               foreach ($relations as $propertyName => $relatedObjects) {
-                       if (is_array($relatedObjects)) {
-                               foreach ($relatedObjects as $relatedObject) {
-                                       $this->deleteObject($relatedObject, $object, $propertyName);
-                                       if ($dataMap->getColumnMap($propertyName)->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
-                                               // SK: This method does IMHO not exist.
-                                               $this->deleteRelationInRelationTable($relatedObject, $object, $propertyName);
+       protected function removeRelatedObjects(Tx_Extbase_DomainObject_DomainObjectInterface $object) {
+               $className = get_class($object);
+               $dataMap = $this->dataMapper->getDataMap($className);
+               $classSchema = $this->reflectionService->getClassSchema($className);
+                               
+               $properties = $object->_getProperties();
+               foreach ($properties as $propertyName => $propertyValue) {
+                       $columnMap = $dataMap->getColumnMap($propertyName);
+                       $propertyMetaData = $classSchema->getProperty($propertyName);
+                       if ($propertyMetaData['cascade'] === 'remove') {
+                               if ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) {
+                                       foreach ($propertyValue as $containedObject) {
+                                               $this->removeObject($containedObject);
                                        }
+                               } elseif ($propertyValue instanceof Tx_Extbase_DomainObject_DomainObjectInterface) {                            
+                                       $this->removeObject($propertyValue);
                                }
                        }
                }
        }
 
-       /**
-        * Update relations in a relation table
-        *
-        * @param array $relatedObjects An array of related objects
-        * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject The parent object
-        * @param string $parentPropertyName The name of the parent object's property where the related objects are stored in
-        * @return void
-        */
-       protected function deleteRelationInRelationTable($relatedObject, Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $parentPropertyName) {
-               $dataMap = $this->dataMapper->getDataMap(get_class($parentObject));
-               $tableName = $dataMap->getColumnMap($parentPropertyName)->getRelationTableName();
-               // TODO Remove dependency to the t3lib_db instance
-               $res = $this->persistenceBackend->exec_SELECTquery(
-                       'uid_foreign',
-                       $tableName,
-                       'uid_local=' . $parentObject->getUid()
-                       );
-               $existingRelations = array();
-               while($row = $this->persistenceBackend->sql_fetch_assoc($res)) {
-                       $existingRelations[current($row)] = current($row);
-               }
-               $relationsToDelete = $existingRelations;
-               if (is_array($relatedObject)) {
-                       foreach ($relatedObject as $relatedObject) {
-                               $relatedObjectUid = $relatedObject->getUid();
-                               if (array_key_exists($relatedObjectUid, $relationsToDelete)) {
-                                       unset($relationsToDelete[$relatedObjectUid]);
-                               }
-                       }
-               }
-               if (count($relationsToDelete) > 0) {
-                       $relationsToDeleteList = implode(',', $relationsToDelete);
-                       $res = $this->persistenceBackend->exec_DELETEquery(
-                               $tableName,
-                               'uid_local=' . $parentObject->getUid() . ' AND uid_foreign IN (' . $relationsToDeleteList . ')'
-                               );
-               }
-       }
-
        /**
         * Delegates the call to the Data Map.
         * Returns TRUE if the property is persistable (configured in $TCA)
@@ -618,6 +784,28 @@ class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendIn
                $dataMap = $this->dataMapper->getDataMap($className);
                return $dataMap->isPersistableProperty($propertyName);
        }
+       
+       /**
+        * Determine the storage page ID for a given NEW record
+        *
+        * This does the following:
+        * - If there is a TypoScript configuration "classes.CLASSNAME.newRecordStoragePid", that is used to store new records.
+        * - If there is no such TypoScript configuration, it uses the first value of The "storagePid" taken for reading records.
+        *
+        * @param Tx_Extbase_DomainObject_DomainObjectInterface $object
+        * @return int the storage Page ID where the object should be stored
+        */
+       protected function determineStoragePageIdForNewRecord(Tx_Extbase_DomainObject_DomainObjectInterface $object) {
+               $className = get_class($object);
+               $extbaseSettings = Tx_Extbase_Dispatcher::getExtbaseFrameworkConfiguration();
+
+               if (isset($extbaseSettings['persistence']['classes'][$className]) && !empty($extbaseSettings['persistence']['classes'][$className]['newRecordStoragePid'])) {
+                       return (int)$extbaseSettings['persistence']['classes'][$className]['newRecordStoragePid'];
+               } else {
+                       $storagePidList = t3lib_div::intExplode(',', $extbaseSettings['persistence']['storagePid']);
+                       return (int) $storagePidList[0];
+               }
+       }
 
 }