diff --git a/ch.eitchnet.xmlpers/LICENSE b/ch.eitchnet.xmlpers/LICENSE
new file mode 100644
index 000000000..d64569567
--- /dev/null
+++ b/ch.eitchnet.xmlpers/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/ch.eitchnet.xmlpers/README.md b/ch.eitchnet.xmlpers/README.md
new file mode 100644
index 000000000..ec5c13c8c
--- /dev/null
+++ b/ch.eitchnet.xmlpers/README.md
@@ -0,0 +1,38 @@
+ch.eitchnet.java.xmlpers
+========================
+
+[![Build Status](http://jenkins.eitchnet.ch/buildStatus/icon?job=ch.eitchnet.xmlpers)](http://jenkins.eitchnet.ch/view/ch.eitchnet/job/ch.eitchnet.xmlpers/)
+
+Generic Java XML persistence layer. Implemented to be light-weight and simple to use
+
+Dependencies
+------------------------
+XmlPers is built by Maven3 and has very few external dependencies. The current dependencies are:
+* the Java Runtime Environment 6
+* ch.eitchnet.utils
+* slf4j 1.7.2
+* slf4j-log4j bindings (only during tests)
+* JUnit 4.10 (only during tests)
+
+Features
+------------------------
+The idea behind XmlPers is to have a very lightweight database where each object is saved in its own XML file.
+
+The model for XmlPers is that for each persistable class the following information is available:
+* Type (e.g. the class name)
+* Optional Sub Type (e.g. some type in your class)
+* Id
+
+This is not forced on the model itself, but in the DAO. Persisting changes is done by delegating to XmlFilePersister and the DAO must convert the object to a XML Document.
+
+See the tests for a reference implementation.
+
+Building
+------------------------
+*Prerequisites:
+ * JDK 6 is installed and JAVA_HOME is properly set and ../bin is in path
+ * Maven 3 is installed and MAVEN_HOME is properly set and ../bin is in path
+ * ch.eitchnet.utils is installed in your local Maven Repository
+* Clone repository and change path to root
+* Run maven:
+ * mvn clean install
diff --git a/ch.eitchnet.xmlpers/pom.xml b/ch.eitchnet.xmlpers/pom.xml
new file mode 100644
index 000000000..4374bde4f
--- /dev/null
+++ b/ch.eitchnet.xmlpers/pom.xml
@@ -0,0 +1,71 @@
+
+
+ 4.0.0
+
+
+ ch.eitchnet
+ ch.eitchnet.parent
+ 1.1.0-SNAPSHOT
+ ../ch.eitchnet.parent/pom.xml
+
+
+ ch.eitchnet.xmlpers
+ jar
+ ch.eitchnet.xmlpers
+ https://github.com/eitchnet/ch.eitchnet.xmlpers
+
+
+ UTF-8
+ 1.1.0-SNAPSHOT
+
+
+
+
+ 2011
+
+
+ Github Issues
+ https://github.com/eitchnet/ch.eitchnet.xmlpers/issues
+
+
+
+ scm:git:https://github.com/eitchnet/ch.eitchnet.xmlpers.git
+ scm:git:git@github.com:eitchnet/ch.eitchnet.xmlpers.git
+ https://github.com/eitchnet/ch.eitchnet.xmlpers
+ HEAD
+
+
+
+
+ ch.eitchnet
+ ch.eitchnet.utils
+ ${eitchnet.utils.version}
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-eclipse-plugin
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+
+
+ org.apache.maven.plugins
+ maven-site-plugin
+
+
+
+
+
diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/DomParser.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/DomParser.java
new file mode 100644
index 000000000..3168364cd
--- /dev/null
+++ b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/DomParser.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 Robert von Burg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ch.eitchnet.xmlpers.api;
+
+import org.w3c.dom.Document;
+
+public interface DomParser {
+
+ public T getObject();
+
+ public void setObject(T object);
+
+ public Document toDom();
+
+ public void fromDom(Document document);
+}
\ No newline at end of file
diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/FileDao.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/FileDao.java
new file mode 100644
index 000000000..8a7e54af4
--- /dev/null
+++ b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/FileDao.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2013 Robert von Burg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ch.eitchnet.xmlpers.api;
+
+import java.io.File;
+import java.text.MessageFormat;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import ch.eitchnet.xmlpers.impl.PathBuilder;
+import ch.eitchnet.xmlpers.objref.ObjectRef;
+
+public class FileDao {
+
+ private static final Logger logger = LoggerFactory.getLogger(FileDao.class);
+
+ private final PersistenceTransaction tx;
+ private final boolean verbose;
+ private final PathBuilder pathBuilder;
+
+ public FileDao(PersistenceTransaction tx, PathBuilder pathBuilder, boolean verbose) {
+ this.tx = tx;
+ this.pathBuilder = pathBuilder;
+ this.verbose = verbose;
+ }
+
+ private void assertIsIdRef(IoOperation ioOperation, ObjectRef objectRef) {
+ if (!objectRef.isLeaf()) {
+ String msg = "A {0} operation can only be performed with IdRefs!"; //$NON-NLS-1$
+ msg = MessageFormat.format(msg, ioOperation);
+ throw new XmlPersistenceException(msg);
+ }
+ }
+
+ public boolean exists(PersistenceContext ctx) {
+ ObjectRef objectRef = ctx.getObjectRef();
+ assertIsIdRef(IoOperation.READ, objectRef);
+ File path = objectRef.getPath(this.pathBuilder);
+ return path.exists();
+ }
+
+ public void performCreate(PersistenceContext ctx) {
+ ObjectRef objectRef = ctx.getObjectRef();
+ assertIsIdRef(IoOperation.CREATE, objectRef);
+ File path = objectRef.getPath(this.pathBuilder);
+ logPath(IoOperation.CREATE, path, objectRef);
+ assertPathNotExists(path, objectRef);
+ createMissingParents(path, objectRef);
+ FileIo fileIo = new FileIo(path);
+ this.tx.getIoMode().write(ctx, fileIo);
+ }
+
+ public void performRead(PersistenceContext ctx) {
+ ObjectRef objectRef = ctx.getObjectRef();
+ assertIsIdRef(IoOperation.READ, objectRef);
+ File path = objectRef.getPath(this.pathBuilder);
+ if (!path.exists()) {
+ ctx.setObject(null);
+ return;
+ }
+
+ logPath(IoOperation.READ, path, objectRef);
+ FileIo fileIo = new FileIo(path);
+ this.tx.getIoMode().read(ctx, fileIo);
+ }
+
+ public void performUpdate(PersistenceContext ctx) {
+ ObjectRef objectRef = ctx.getObjectRef();
+ assertIsIdRef(IoOperation.UPDATE, objectRef);
+ File path = objectRef.getPath(this.pathBuilder);
+ logPath(IoOperation.UPDATE, path, objectRef);
+ assertPathIsFileAndWritable(path, objectRef);
+ FileIo fileIo = new FileIo(path);
+ this.tx.getIoMode().write(ctx, fileIo);
+ }
+
+ public void performDelete(PersistenceContext ctx) {
+ ObjectRef objectRef = ctx.getObjectRef();
+ assertIsIdRef(IoOperation.DELETE, objectRef);
+ File path = objectRef.getPath(this.pathBuilder);
+ logPath(IoOperation.DELETE, path, objectRef);
+ assertPathIsFileAndWritable(path, objectRef);
+ if (!path.delete()) {
+ String msg = "Failed to delete file {0}"; //$NON-NLS-1$
+ throw new RuntimeException(MessageFormat.format(msg, path.getAbsolutePath()));
+ }
+
+ ObjectRef parentRef = objectRef.getParent(this.tx);
+ deleteEmptyDirectories(parentRef);
+ }
+
+ private void deleteEmptyDirectories(ObjectRef objectRef) {
+
+ // root can't be deleted
+ if (objectRef.isRoot())
+ return;
+
+ if (objectRef.isLeaf()) {
+ throw new IllegalArgumentException("IdRefs don't reference directories!"); //$NON-NLS-1$
+ }
+
+ objectRef.lock();
+
+ try {
+
+ File directoryPath = objectRef.getPath(this.pathBuilder);
+ if (!directoryPath.isDirectory()) {
+ String msg = "The path for {0} is not a directory: {1}"; //$NON-NLS-1$
+ msg = MessageFormat.format(msg, objectRef.getName(), directoryPath.getAbsolutePath());
+ throw new IllegalArgumentException(msg);
+ }
+
+ // stop if empty
+ if (directoryPath.list().length != 0)
+ return;
+
+ // delete
+ if (!directoryPath.delete()) {
+ String msg = "Deletion of empty directory for {0} at {1} failed! Check file permissions!"; //$NON-NLS-1$
+ msg = MessageFormat.format(msg, objectRef.getName(), directoryPath.getAbsolutePath());
+ throw new XmlPersistenceException(msg);
+ }
+
+ // log
+ if (this.verbose) {
+ String msg = "Deleted empty directory for {0} at {1}"; //$NON-NLS-1$
+ logger.info(MessageFormat.format(msg, objectRef.getName(), directoryPath));
+ }
+
+ // recursively delete
+ ObjectRef parent = objectRef.getParent(this.tx);
+ deleteEmptyDirectories(parent);
+
+ } finally {
+ objectRef.unlock();
+ }
+ }
+
+ private void logPath(IoOperation operation, File path, ObjectRef objectRef) {
+ if (this.verbose) {
+ String msg = "Path for operation {0} for {1} is at {2}"; //$NON-NLS-1$
+ msg = MessageFormat.format(msg, operation, objectRef.getName(), path.getAbsolutePath());
+ logger.info(msg);
+ }
+ }
+
+ private void createMissingParents(File path, ObjectRef objectRef) {
+ ObjectRef parentRef = objectRef.getParent(this.tx);
+ parentRef.lock();
+ try {
+ File parentFile = parentRef.getPath(this.pathBuilder);
+ if (!parentFile.exists() && !parentFile.mkdirs()) {
+ String msg = "Could not create parent path for {0} at {1}"; //$NON-NLS-1$
+ msg = MessageFormat.format(msg, objectRef.getName(), path.getAbsolutePath());
+ throw new XmlPersistenceException(msg);
+ }
+ } finally {
+ parentRef.unlock();
+ }
+ }
+
+ private void assertPathIsFileAndWritable(File path, ObjectRef objectRef) {
+ if (!path.exists()) {
+ String msg = "Persistence unit does not exist for {0} at {1}"; //$NON-NLS-1$
+ msg = MessageFormat.format(msg, objectRef.getName(), path.getAbsolutePath());
+ throw new XmlPersistenceException(msg);
+ }
+
+ if (!path.isFile() || !path.canWrite()) {
+ String msg;
+ msg = "Persistence unit is not a file or is not readable for {0} at {1}"; //$NON-NLS-1$
+ msg = MessageFormat.format(msg, objectRef.getName(), path.getAbsolutePath());
+ throw new XmlPersistenceException(msg);
+ }
+ }
+
+ private void assertPathNotExists(File path, ObjectRef objectRef) {
+ if (path.exists()) {
+ String msg = "Persistence unit already exists for {0} at {1}"; //$NON-NLS-1$
+ msg = MessageFormat.format(msg, objectRef.getName(), path.getAbsolutePath());
+ throw new XmlPersistenceException(msg);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/FileIo.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/FileIo.java
new file mode 100644
index 000000000..9aaa97e45
--- /dev/null
+++ b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/FileIo.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright 2013 Robert von Burg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ch.eitchnet.xmlpers.api;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.text.MessageFormat;
+
+import javanet.staxutils.IndentingXMLStreamWriter;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+import javax.xml.stream.FactoryConfigurationError;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.TransformerFactoryConfigurationError;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import ch.eitchnet.utils.exceptions.XmlException;
+import ch.eitchnet.utils.helper.StringHelper;
+import ch.eitchnet.utils.helper.XmlHelper;
+import ch.eitchnet.xmlpers.util.DomUtil;
+
+public class FileIo {
+
+ public static final String DEFAULT_XML_VERSION = "1.0"; //$NON-NLS-1$
+ public static final String DEFAULT_ENCODING = "utf-8"; //$NON-NLS-1$
+
+ private static final Logger logger = LoggerFactory.getLogger(FileIo.class);
+
+ private final File path;
+
+ public FileIo(File path) {
+ this.path = path;
+ }
+
+ public void writeSax(PersistenceContext ctx) {
+
+ XMLStreamWriter writer = null;
+ try {
+ try (FileWriter fileWriter = new FileWriter(this.path);) {
+
+ XMLOutputFactory factory = XMLOutputFactory.newInstance();
+ writer = factory.createXMLStreamWriter(fileWriter);
+ writer = new IndentingXMLStreamWriter(writer);
+
+ // start document
+ writer.writeStartDocument(DEFAULT_ENCODING, DEFAULT_XML_VERSION);
+
+ // then delegate object writing to caller
+ SaxParser saxParser = ctx.getParserFactor().getSaxParser();
+ saxParser.setObject(ctx.getObject());
+ saxParser.write(writer);
+
+ // and now end
+ writer.writeEndDocument();
+ writer.flush();
+ }
+
+ } catch (FactoryConfigurationError | XMLStreamException | IOException e) {
+ if (this.path.exists())
+ this.path.delete();
+ String msg = "Writing to file failed due to internal error: {0}"; //$NON-NLS-1$
+ msg = MessageFormat.format(msg, e.getMessage());
+ throw new XmlException(msg, e);
+ }
+
+ if (logger.isDebugEnabled()) {
+ String msg = "Wrote SAX to {0}"; //$NON-NLS-1$
+ logger.info(MessageFormat.format(msg, this.path.getAbsolutePath()));
+ }
+ }
+
+ public void readSax(PersistenceContext ctx) {
+
+ try {
+
+ SAXParserFactory spf = SAXParserFactory.newInstance();
+ SAXParser sp = spf.newSAXParser();
+
+ SaxParser saxParser = ctx.getParserFactor().getSaxParser();
+ DefaultHandler defaultHandler = saxParser.getDefaultHandler();
+ sp.parse(this.path, defaultHandler);
+
+ if (logger.isDebugEnabled()) {
+ String msg = "SAX parsed file {0}"; //$NON-NLS-1$
+ logger.info(MessageFormat.format(msg, this.path.getAbsolutePath()));
+ }
+
+ ctx.setObject(saxParser.getObject());
+
+ } catch (ParserConfigurationException | SAXException | IOException e) {
+
+ String msg = "Parsing failed due to internal error: {0}"; //$NON-NLS-1$
+ throw new XmlPersistenceException(MessageFormat.format(msg, e.getMessage()), e);
+ }
+ }
+
+ public void writeDom(PersistenceContext ctx) {
+
+ String lineSep = System.getProperty(XmlHelper.PROP_LINE_SEPARATOR);
+
+ try {
+
+ DomParser domParser = ctx.getParserFactor().getDomParser();
+ domParser.setObject(ctx.getObject());
+ Document document = domParser.toDom();
+ String encoding = document.getInputEncoding();
+ if (encoding == null || encoding.isEmpty()) {
+ // logger.info("No encoding passed. Using default encoding " + XmlHelper.DEFAULT_ENCODING);
+ encoding = XmlHelper.DEFAULT_ENCODING;
+ }
+
+ if (!lineSep.equals(StringHelper.NEW_LINE)) {
+ logger.info("Overriding line separator to \\n"); //$NON-NLS-1$
+ System.setProperty(XmlHelper.PROP_LINE_SEPARATOR, StringHelper.NEW_LINE);
+ }
+
+ // Set up a transformer
+ TransformerFactory transfac = TransformerFactory.newInstance();
+ Transformer transformer = transfac.newTransformer();
+ transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); //$NON-NLS-1$
+ transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$
+ transformer.setOutputProperty(OutputKeys.METHOD, "xml"); //$NON-NLS-1$
+ transformer.setOutputProperty(OutputKeys.ENCODING, encoding);
+ transformer.setOutputProperty("{http://xml.apache.org/xalan}indent-amount", "2"); //$NON-NLS-1$ //$NON-NLS-2$
+ // transformer.setOutputProperty("{http://xml.apache.org/xalan}line-separator", "\t");
+
+ // Transform to file
+ StreamResult result = new StreamResult(this.path);
+ Source xmlSource = new DOMSource(document);
+ transformer.transform(xmlSource, result);
+
+ if (logger.isDebugEnabled()) {
+ String msg = MessageFormat.format("Wrote DOM to {0}", this.path.getAbsolutePath()); //$NON-NLS-1$
+ logger.info(msg);
+ }
+
+ } catch (TransformerFactoryConfigurationError | TransformerException e) {
+
+ if (this.path.exists())
+ this.path.delete();
+
+ String msg = "Writing to file failed due to internal error: {0}"; //$NON-NLS-1$
+ msg = MessageFormat.format(msg, e.getMessage());
+ throw new XmlException(msg, e);
+
+ } finally {
+ System.setProperty(XmlHelper.PROP_LINE_SEPARATOR, lineSep);
+ }
+ }
+
+ public void readDom(PersistenceContext ctx) {
+
+ try {
+
+ DocumentBuilder docBuilder = DomUtil.createDocumentBuilder();
+ Document document = docBuilder.parse(this.path);
+ DomParser domParser = ctx.getParserFactor().getDomParser();
+ domParser.fromDom(document);
+
+ if (logger.isDebugEnabled()) {
+ String msg = "DOM parsed file {0}"; //$NON-NLS-1$
+ msg = MessageFormat.format(msg, this.path.getAbsolutePath());
+ logger.info(msg);
+ }
+
+ ctx.setObject(domParser.getObject());
+
+ } catch (SAXException | IOException e) {
+ String msg = "Parsing failed due to internal error: {0}"; //$NON-NLS-1$
+ msg = MessageFormat.format(msg, e.getMessage());
+ throw new XmlPersistenceException(msg, e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/IoMode.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/IoMode.java
new file mode 100644
index 000000000..7e5c7ee52
--- /dev/null
+++ b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/IoMode.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2013 Robert von Burg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ch.eitchnet.xmlpers.api;
+
+/**
+ * @author Robert von Burg
+ *
+ */
+public enum IoMode {
+
+ DOM {
+ @Override
+ public void write(PersistenceContext ctx, FileIo fileIo) {
+ fileIo.writeDom(ctx);
+ }
+
+ @Override
+ public void read(PersistenceContext ctx, FileIo fileIo) {
+ fileIo.readDom(ctx);
+ }
+ },
+ SAX {
+ @Override
+ public void write(PersistenceContext ctx, FileIo fileIo) {
+ fileIo.writeSax(ctx);
+ }
+
+ @Override
+ public void read(PersistenceContext ctx, FileIo fileIo) {
+ fileIo.readSax(ctx);
+ }
+ };
+
+ /**
+ * @param ctx
+ * @param fileIo
+ */
+ public void write(PersistenceContext ctx, FileIo fileIo) {
+ throw new UnsupportedOperationException("Override me!"); //$NON-NLS-1$
+ }
+
+ /**
+ * @param ctx
+ * @param fileIo
+ */
+ public void read(PersistenceContext ctx, FileIo fileIo) {
+ throw new UnsupportedOperationException("Override me!"); //$NON-NLS-1$
+ }
+}
diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/IoOperation.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/IoOperation.java
new file mode 100644
index 000000000..ff4fd47ed
--- /dev/null
+++ b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/IoOperation.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2013 Robert von Burg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ch.eitchnet.xmlpers.api;
+
+public enum IoOperation {
+
+ CREATE, READ, UPDATE, DELETE;
+}
diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/MetadataDao.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/MetadataDao.java
new file mode 100644
index 000000000..7a114bb7a
--- /dev/null
+++ b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/MetadataDao.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright 2013 Robert von Burg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ch.eitchnet.xmlpers.api;
+
+import java.io.File;
+import java.text.MessageFormat;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import ch.eitchnet.xmlpers.impl.PathBuilder;
+import ch.eitchnet.xmlpers.objref.ObjectRef;
+import ch.eitchnet.xmlpers.util.FilenameUtility;
+
+/**
+ * @author Robert von Burg
+ *
+ */
+public class MetadataDao {
+
+ private static final Logger logger = LoggerFactory.getLogger(MetadataDao.class);
+
+ private final PersistenceTransaction tx;
+ private final PathBuilder pathBuilder;
+ private final boolean verbose;
+
+ public MetadataDao(PathBuilder pathBuilder, PersistenceTransaction tx, boolean verbose) {
+ this.tx = tx;
+ this.pathBuilder = pathBuilder;
+ this.verbose = verbose;
+ }
+
+ public Set queryTypeSet(ObjectRef parentRef) {
+ assertNotClosed(this.tx);
+ assertNotIdRef(parentRef);
+
+ parentRef.lock();
+ try {
+ File queryPath = parentRef.getPath(this.pathBuilder);
+ Set keySet = queryTypeSet(queryPath);
+
+ if (this.verbose) {
+ String msg = "Found {0} types for {1}"; //$NON-NLS-1$
+ msg = MessageFormat.format(msg, keySet.size(), parentRef.getName());
+ logger.info(msg);
+ }
+
+ return keySet;
+ } finally {
+ parentRef.unlock();
+ }
+ }
+
+ public Set queryKeySet(ObjectRef parentRef) {
+ assertNotClosed(this.tx);
+ assertNotRootRef(parentRef);
+ assertNotIdRef(parentRef);
+
+ parentRef.lock();
+ try {
+ File queryPath = parentRef.getPath(this.pathBuilder);
+ Set keySet = queryKeySet(queryPath);
+
+ if (this.verbose) {
+ String msg = "Found {0} objects for {1}"; //$NON-NLS-1$
+ msg = MessageFormat.format(msg, keySet.size(), parentRef.getName());
+ logger.info(msg);
+ }
+
+ return keySet;
+ } finally {
+ parentRef.unlock();
+ }
+ }
+
+ public long queryTypeSize(ObjectRef parentRef) {
+ assertNotClosed(this.tx);
+ assertNotRootRef(parentRef);
+ assertNotIdRef(parentRef);
+
+ parentRef.lock();
+ try {
+ File queryPath = parentRef.getPath(this.pathBuilder);
+ long numberOfFiles = queryTypeSize(queryPath);
+
+ if (this.verbose) {
+ String msg = "Found {0} types for {1}"; //$NON-NLS-1$
+ msg = MessageFormat.format(msg, numberOfFiles, parentRef.getName());
+ logger.info(msg);
+ }
+
+ return numberOfFiles;
+ } finally {
+ parentRef.unlock();
+ }
+ }
+
+ public long querySize(ObjectRef parentRef) {
+ assertNotClosed(this.tx);
+
+ parentRef.lock();
+ try {
+ File queryPath = parentRef.getPath(this.pathBuilder);
+ long numberOfFiles = querySize(queryPath);
+
+ if (this.verbose) {
+ String msg = "Found {0} objects for {1}"; //$NON-NLS-1$
+ msg = MessageFormat.format(msg, numberOfFiles, parentRef.getName());
+ logger.info(msg);
+ }
+
+ return numberOfFiles;
+ } finally {
+ parentRef.unlock();
+ }
+ }
+
+ /**
+ * Returns the types, i.e. directories in the given query path
+ *
+ * @param queryPath
+ * the path for which the types should be gathered
+ *
+ * @return a set of types in the given query path
+ */
+ private Set queryTypeSet(File queryPath) {
+ if (!queryPath.exists())
+ return Collections.emptySet();
+
+ if (!queryPath.isDirectory()) {
+ String msg = "The path is not a directory, thus can not query type set for it: {0}"; //$NON-NLS-1$
+ msg = MessageFormat.format(msg, queryPath.getAbsolutePath());
+ throw new IllegalArgumentException(msg);
+ }
+
+ Set keySet = new HashSet<>();
+ File[] subTypeFiles = queryPath.listFiles();
+ for (File subTypeFile : subTypeFiles) {
+ if (subTypeFile.isDirectory()) {
+ String type = subTypeFile.getName();
+ keySet.add(type);
+ }
+ }
+
+ return keySet;
+ }
+
+ /**
+ * Returns the ids of all objects in the given query path, i.e. the id part of all the files in the given query path
+ *
+ * @param queryPath
+ * the path for which the ids should be gathered
+ *
+ * @return a set of ids for the objects in the given query path
+ */
+ private Set queryKeySet(File queryPath) {
+ if (!queryPath.exists())
+ return Collections.emptySet();
+
+ if (!queryPath.isDirectory()) {
+ String msg = "The path is not a directory, thus can not query key set for it: {0}"; //$NON-NLS-1$
+ msg = MessageFormat.format(msg, queryPath.getAbsolutePath());
+ throw new IllegalArgumentException(msg);
+ }
+
+ Set keySet = new HashSet<>();
+
+ File[] subTypeFiles = queryPath.listFiles();
+ for (File subTypeFile : subTypeFiles) {
+ if (subTypeFile.isFile()) {
+ String filename = subTypeFile.getName();
+ String id = FilenameUtility.getId(filename);
+ keySet.add(id);
+ }
+ }
+
+ return keySet;
+ }
+
+ /**
+ * Returns the number of all types, i.e. directories in the given query path
+ *
+ * @param queryPath
+ * the path in which to count the types
+ *
+ * @return the number of types in the given query path
+ */
+ private long queryTypeSize(File queryPath) {
+ if (!queryPath.exists())
+ return 0L;
+
+ if (!queryPath.isDirectory()) {
+ String msg = "The path is not a directory, thus can not query type size for it: {0}"; //$NON-NLS-1$
+ msg = MessageFormat.format(msg, queryPath.getAbsolutePath());
+ throw new IllegalArgumentException(msg);
+ }
+
+ long numberOfFiles = 0l;
+
+ File[] subTypeFiles = queryPath.listFiles();
+ for (File subTypeFile : subTypeFiles) {
+
+ if (subTypeFile.isDirectory())
+ numberOfFiles++;
+ }
+ return numberOfFiles;
+ }
+
+ /**
+ * Returns the number of all objects in the given query path
+ *
+ * @param queryPath
+ * the path in which to count the objects
+ *
+ * @return the number of objects in the given query path
+ */
+ private long querySize(File queryPath) {
+ if (!queryPath.exists())
+ return 0L;
+
+ if (!queryPath.isDirectory()) {
+ String msg = "The path is not a directory, thus can not query key size for it: {0}"; //$NON-NLS-1$
+ msg = MessageFormat.format(msg, queryPath.getAbsolutePath());
+ throw new IllegalArgumentException(msg);
+ }
+
+ long numberOfFiles = 0l;
+
+ File[] subTypeFiles = queryPath.listFiles();
+ for (File subTypeFile : subTypeFiles) {
+
+ if (subTypeFile.isFile())
+ numberOfFiles++;
+ }
+ return numberOfFiles;
+ }
+
+ private void assertNotClosed(PersistenceTransaction tx) {
+ if (!tx.isOpen()) {
+ String msg = "Transaction has been closed and thus no operation can be performed!"; //$NON-NLS-1$
+ throw new IllegalStateException(msg);
+ }
+ }
+
+ private void assertNotIdRef(ObjectRef objectRef) {
+ if (objectRef.isLeaf()) {
+ String msg = "IdRef not allowed: {0}"; //$NON-NLS-1$
+ msg = MessageFormat.format(msg, objectRef.getName());
+ throw new IllegalArgumentException(msg);
+ }
+ }
+
+ private void assertNotRootRef(ObjectRef objectRef) {
+ if (objectRef.isRoot()) {
+ String msg = "RootRef not allowed: {0}"; //$NON-NLS-1$
+ msg = MessageFormat.format(msg, objectRef.getName());
+ throw new IllegalArgumentException(msg);
+ }
+ }
+}
diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/ModificationResult.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/ModificationResult.java
new file mode 100644
index 000000000..761513488
--- /dev/null
+++ b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/ModificationResult.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2013 Robert von Burg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ch.eitchnet.xmlpers.api;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ModificationResult {
+
+ private final String key;
+ private final List> created;
+ private final List> updated;
+ private final List> deleted;
+
+ public ModificationResult(String key) {
+ this.key = key;
+ this.created = new ArrayList<>();
+ this.updated = new ArrayList<>();
+ this.deleted = new ArrayList<>();
+ }
+
+ public ModificationResult(String key, List> created, List> updated, List> deleted) {
+ this.key = key;
+ this.created = created;
+ this.updated = updated;
+ this.deleted = deleted;
+ }
+
+ public String getKey() {
+ return this.key;
+ }
+
+ @SuppressWarnings("unchecked")
+ public List getCreated() {
+ return (List) this.created;
+ }
+
+ @SuppressWarnings("unchecked")
+ public List getUpdated() {
+ return (List) this.updated;
+ }
+
+ @SuppressWarnings("unchecked")
+ public List getDeleted() {
+ return (List) this.deleted;
+ }
+}
diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java
new file mode 100644
index 000000000..493a749c8
--- /dev/null
+++ b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/ObjectDao.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright 2013 Robert von Burg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ch.eitchnet.xmlpers.api;
+
+import static ch.eitchnet.xmlpers.util.AssertionUtil.assertIsIdRef;
+import static ch.eitchnet.xmlpers.util.AssertionUtil.assertIsNotIdRef;
+import static ch.eitchnet.xmlpers.util.AssertionUtil.assertIsNotRootRef;
+import static ch.eitchnet.xmlpers.util.AssertionUtil.assertNotNull;
+import static ch.eitchnet.xmlpers.util.AssertionUtil.assertObjectRead;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import ch.eitchnet.utils.objectfilter.ObjectFilter;
+import ch.eitchnet.xmlpers.objref.ObjectRef;
+import ch.eitchnet.xmlpers.objref.SubTypeRef;
+import ch.eitchnet.xmlpers.objref.TypeRef;
+
+/**
+ * @author Robert von Burg
+ *
+ */
+public class ObjectDao {
+
+ private final ObjectFilter objectFilter;
+ private final FileDao fileDao;
+ private final PersistenceTransaction tx;
+ private PersistenceContextFactoryDelegator ctxFactoryDelegator;
+
+ public ObjectDao(PersistenceTransaction tx, FileDao fileDao, ObjectFilter objectFilter) {
+ this.tx = tx;
+ this.fileDao = fileDao;
+ this.objectFilter = objectFilter;
+ this.ctxFactoryDelegator = this.tx.getRealm().getCtxFactoryDelegator();
+ }
+
+ public void add(T object) {
+ assertNotClosed();
+ assertNotNull(object);
+ PersistenceContext ctx = createCtx(object);
+ ctx.getObjectRef().lock();
+ this.objectFilter.add(ctx.getObjectRef().getType(), ctx);
+ }
+
+ public void addAll(List objects) {
+ assertNotClosed();
+ assertNotNull(objects);
+ if (!objects.isEmpty()) {
+ for (T object : objects) {
+ PersistenceContext ctx = createCtx(object);
+ ctx.getObjectRef().lock();
+ this.objectFilter.add(ctx.getObjectRef().getType(), ctx);
+ }
+ }
+ }
+
+ public void update(T object) {
+ assertNotClosed();
+ assertNotNull(object);
+ PersistenceContext ctx = createCtx(object);
+ ctx.getObjectRef().lock();
+ this.objectFilter.update(ctx.getObjectRef().getType(), ctx);
+ }
+
+ public void updateAll(List objects) {
+ assertNotClosed();
+ assertNotNull(objects);
+ if (!objects.isEmpty()) {
+ for (T object : objects) {
+ PersistenceContext ctx = createCtx(object);
+ ctx.getObjectRef().lock();
+ this.objectFilter.update(ctx.getObjectRef().getType(), ctx);
+ }
+ }
+ }
+
+ public void remove(T object) {
+ assertNotClosed();
+ assertNotNull(object);
+ PersistenceContext ctx = createCtx(object);
+ ctx.getObjectRef().lock();
+ this.objectFilter.remove(ctx.getObjectRef().getType(), ctx);
+ }
+
+ public void removeAll(List objects) {
+ assertNotClosed();
+ assertNotNull(objects);
+ if (!objects.isEmpty()) {
+ for (T object : objects) {
+ PersistenceContext ctx = createCtx(object);
+ ctx.getObjectRef().lock();
+ this.objectFilter.remove(ctx.getObjectRef().getType(), ctx);
+ }
+ }
+ }
+
+ public long removeAllBy(TypeRef typeRef) {
+ assertNotClosed();
+
+ long removed = 0;
+
+ Set refs = new HashSet<>();
+ typeRef.lock();
+ refs.add(typeRef);
+ try {
+
+ Set types = this.tx.getMetadataDao().queryTypeSet(typeRef);
+ for (String type : types) {
+ ObjectRef childTypeRef = typeRef.getChildTypeRef(this.tx, type);
+ childTypeRef.lock();
+ refs.add(childTypeRef);
+
+ Set ids = queryKeySet(childTypeRef);
+ for (String id : ids) {
+
+ ObjectRef idRef = childTypeRef.getChildIdRef(this.tx, id);
+
+ PersistenceContext ctx = createCtx(idRef);
+ ctx.getObjectRef().lock();
+ this.objectFilter.remove(ctx.getObjectRef().getType(), ctx);
+ removed++;
+ }
+ }
+ } finally {
+ for (ObjectRef ref : refs) {
+ ref.unlock();
+ }
+ }
+
+ return removed;
+ }
+
+ public long removeAllBy(SubTypeRef subTypeRef) {
+ assertNotClosed();
+ assertIsNotRootRef(subTypeRef);
+ assertIsNotIdRef(subTypeRef);
+
+ long removed = 0;
+
+ subTypeRef.lock();
+ try {
+ Set ids = queryKeySet(subTypeRef);
+ for (String id : ids) {
+
+ ObjectRef idRef = subTypeRef.getChildIdRef(this.tx, id);
+
+ PersistenceContext ctx = createCtx(idRef);
+ ctx.getObjectRef().lock();
+ this.objectFilter.remove(ctx.getObjectRef().getType(), ctx);
+ removed++;
+ }
+ } finally {
+ subTypeRef.unlock();
+ }
+
+ return removed;
+ }
+
+ public void removeById(ObjectRef objectRef) {
+ assertNotClosed();
+ assertIsIdRef(objectRef);
+ PersistenceContext ctx = createCtx(objectRef);
+ ctx.getObjectRef().lock();
+ this.objectFilter.remove(objectRef.getType(), ctx);
+ }
+
+ public void removeAll(ObjectRef parentRef) {
+ assertNotClosed();
+ assertIsNotIdRef(parentRef);
+ assertIsNotRootRef(parentRef);
+
+ parentRef.lock();
+ try {
+
+ Set keySet = queryKeySet(parentRef);
+ for (String id : keySet) {
+
+ ObjectRef childRef = parentRef.getChildIdRef(this.tx, id);
+ PersistenceContext ctx = createCtx(childRef);
+ ctx.getObjectRef().lock();
+ this.objectFilter.remove(childRef.getType(), ctx);
+ }
+ } finally {
+ parentRef.unlock();
+ }
+ }
+
+ public boolean hasElement(ObjectRef objectRef) {
+ assertNotClosed();
+ assertIsIdRef(objectRef);
+
+ objectRef.lock();
+ try {
+ PersistenceContext ctx = objectRef. createPersistenceContext(this.tx);
+ return this.fileDao.exists(ctx);
+ } finally {
+ objectRef.unlock();
+ }
+ }
+
+ public T queryById(ObjectRef objectRef) {
+ assertNotClosed();
+ assertIsIdRef(objectRef);
+
+ objectRef.lock();
+ try {
+ PersistenceContext ctx = objectRef. createPersistenceContext(this.tx);
+ this.fileDao.performRead(ctx);
+ return ctx.getObject();
+ } finally {
+ objectRef.unlock();
+ }
+ }
+
+ public List queryAll(ObjectRef parentRef) {
+ assertNotClosed();
+ assertIsNotIdRef(parentRef);
+
+ parentRef.lock();
+ try {
+
+ MetadataDao metadataDao = this.tx.getMetadataDao();
+ Set keySet = metadataDao.queryKeySet(parentRef);
+
+ List result = new ArrayList<>();
+ for (String id : keySet) {
+
+ ObjectRef childRef = parentRef.getChildIdRef(this.tx, id);
+ PersistenceContext childCtx = childRef.createPersistenceContext(this.tx);
+ childCtx.getObjectRef().lock();
+ try {
+ this.fileDao.performRead(childCtx);
+ assertObjectRead(childCtx);
+ result.add(childCtx.getObject());
+ } finally {
+ childCtx.getObjectRef().unlock();
+ }
+ }
+
+ return result;
+
+ } finally {
+ parentRef.unlock();
+ }
+ }
+
+ public Set queryKeySet(ObjectRef parentRef) {
+ assertNotClosed();
+ assertIsNotIdRef(parentRef);
+
+ parentRef.lock();
+ try {
+ MetadataDao metadataDao = this.tx.getMetadataDao();
+ Set keySet = metadataDao.queryKeySet(parentRef);
+ return keySet;
+ } finally {
+ parentRef.unlock();
+ }
+ }
+
+ public long querySize(ObjectRef parentRef) {
+ assertNotClosed();
+ assertIsNotIdRef(parentRef);
+
+ parentRef.lock();
+ try {
+ MetadataDao metadataDao = this.tx.getMetadataDao();
+ long size = metadataDao.querySize(parentRef);
+ return size;
+ } finally {
+ parentRef.unlock();
+ }
+ }
+
+ public PersistenceContext createCtx(T object) {
+ return this.ctxFactoryDelegator. getCtxFactory(object.getClass()).createCtx(this.tx.getObjectRefCache(),
+ object);
+ }
+
+ public PersistenceContext createCtx(ObjectRef objectRef) {
+ String type = objectRef.getType();
+ PersistenceContextFactory ctxFactory = this.ctxFactoryDelegator. getCtxFactory(type);
+ return ctxFactory.createCtx(objectRef);
+ }
+
+ private void assertNotClosed() {
+ if (!this.tx.isOpen())
+ throw new IllegalStateException("Transaction has been closed and thus no operation can be performed!"); //$NON-NLS-1$
+ }
+}
diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/ParserFactory.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/ParserFactory.java
new file mode 100644
index 000000000..003b1236d
--- /dev/null
+++ b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/ParserFactory.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2013 Robert von Burg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ch.eitchnet.xmlpers.api;
+
+public interface ParserFactory {
+
+ public DomParser getDomParser();
+
+ public SaxParser getSaxParser();
+}
\ No newline at end of file
diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceConstants.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceConstants.java
new file mode 100644
index 000000000..40fd39738
--- /dev/null
+++ b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceConstants.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2013 Robert von Burg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ch.eitchnet.xmlpers.api;
+
+/**
+ * @author Robert von Burg
+ *
+ */
+@SuppressWarnings("nls")
+public class PersistenceConstants {
+
+ private static final String PROP_PREFIX = "ch.eitchnet.xmlpers.";
+ public static final String PROP_VERBOSE = PROP_PREFIX + "verbose";
+ public static final String PROP_BASEPATH = PROP_PREFIX + "basePath";
+ public static final String PROP_DAO_FACTORY_CLASS = PROP_PREFIX + "daoFactoryClass";
+ public static final String PROP_XML_IO_MOD = PROP_PREFIX + "ioMode";
+ public static final String PROP_LOCK_TIME_MILLIS = PROP_PREFIX + "lockTimeSeconds";
+}
diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java
new file mode 100644
index 000000000..0faa8688b
--- /dev/null
+++ b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContext.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2013 Robert von Burg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ch.eitchnet.xmlpers.api;
+
+import ch.eitchnet.xmlpers.objref.ObjectRef;
+
+public class PersistenceContext {
+
+ private final ObjectRef objectRef;
+ private T object;
+ private ParserFactory parserFactory;
+
+ public PersistenceContext(ObjectRef objectRef) {
+ this.objectRef = objectRef;
+ }
+
+ public ObjectRef getObjectRef() {
+ return this.objectRef;
+ }
+
+ public T getObject() {
+ return this.object;
+ }
+
+ public void setObject(T object) {
+ this.object = object;
+ }
+
+ public ParserFactory getParserFactor() {
+ return this.parserFactory;
+ }
+
+ public void setParserFactory(ParserFactory parserFactory) {
+ this.parserFactory = parserFactory;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((this.objectRef == null) ? 0 : this.objectRef.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ PersistenceContext> other = (PersistenceContext>) obj;
+ if (this.objectRef == null) {
+ if (other.objectRef != null)
+ return false;
+ } else if (!this.objectRef.equals(other.objectRef))
+ return false;
+ return true;
+ }
+
+ @SuppressWarnings("nls")
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("PersistenceContext [objectRef=");
+ builder.append(this.objectRef);
+ builder.append(", object=");
+ builder.append(this.object);
+ builder.append(", parserFactory=");
+ builder.append(this.parserFactory);
+ builder.append("]");
+ return builder.toString();
+ }
+}
\ No newline at end of file
diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactory.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactory.java
new file mode 100644
index 000000000..3feb45dd9
--- /dev/null
+++ b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactory.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2013 Robert von Burg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ch.eitchnet.xmlpers.api;
+
+import ch.eitchnet.xmlpers.objref.ObjectRef;
+import ch.eitchnet.xmlpers.objref.ObjectReferenceCache;
+
+public interface PersistenceContextFactory {
+
+ public PersistenceContext createCtx(ObjectRef objectRef);
+
+ public PersistenceContext createCtx(ObjectReferenceCache objectRefCache, T t);
+
+}
\ No newline at end of file
diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactoryDelegator.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactoryDelegator.java
new file mode 100644
index 000000000..8be71335d
--- /dev/null
+++ b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceContextFactoryDelegator.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2013 Robert von Burg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ch.eitchnet.xmlpers.api;
+
+import java.text.MessageFormat;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Robert von Burg
+ *
+ */
+public class PersistenceContextFactoryDelegator {
+
+ private Map> contextFactoryCacheByType;
+ private Map, PersistenceContextFactory>> contextFactoryCacheByClass;
+
+ public PersistenceContextFactoryDelegator() {
+ this.contextFactoryCacheByType = new HashMap<>();
+ this.contextFactoryCacheByClass = new HashMap<>();
+ }
+
+ public void registerPersistenceContextFactory(Class> classType, String type,
+ PersistenceContextFactory> ctxFactory) {
+
+ this.contextFactoryCacheByClass.put(classType, ctxFactory);
+ this.contextFactoryCacheByType.put(type, ctxFactory);
+ }
+
+ public PersistenceContextFactory getCtxFactory(Class> classType) {
+
+ @SuppressWarnings("unchecked")
+ PersistenceContextFactory ctxFactory = (PersistenceContextFactory) this.contextFactoryCacheByClass
+ .get(classType);
+ if (ctxFactory != null)
+ return ctxFactory;
+
+ String msg = "No context factory is registered for {0}"; //$NON-NLS-1$
+ msg = MessageFormat.format(msg, classType);
+ throw new IllegalArgumentException(msg);
+ }
+
+ public PersistenceContextFactory getCtxFactory(String type) {
+
+ @SuppressWarnings("unchecked")
+ PersistenceContextFactory ctxFactory = (PersistenceContextFactory) this.contextFactoryCacheByType
+ .get(type);
+ if (ctxFactory != null)
+ return ctxFactory;
+
+ String msg = "No context factory is registered for type {0}"; //$NON-NLS-1$
+ msg = MessageFormat.format(msg, type);
+ throw new IllegalArgumentException(msg);
+ }
+}
diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManager.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManager.java
new file mode 100644
index 000000000..e19b71521
--- /dev/null
+++ b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManager.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2013 Robert von Burg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ch.eitchnet.xmlpers.api;
+
+/**
+ * @author Robert von Burg
+ *
+ */
+public interface PersistenceManager {
+
+ public static final String DEFAULT_REALM = "defaultRealm"; //$NON-NLS-1$
+
+ public PersistenceContextFactoryDelegator getCtxFactory();
+
+ public PersistenceTransaction openTx();
+
+ public PersistenceTransaction openTx(String realm);
+}
diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManagerLoader.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManagerLoader.java
new file mode 100644
index 000000000..ecbf0ba6d
--- /dev/null
+++ b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceManagerLoader.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2013 Robert von Burg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ch.eitchnet.xmlpers.api;
+
+import java.util.Properties;
+
+import ch.eitchnet.xmlpers.impl.DefaultPersistenceManager;
+
+/**
+ * @author Robert von Burg
+ *
+ */
+public class PersistenceManagerLoader {
+
+ public static PersistenceManager load(Properties properties) {
+
+ DefaultPersistenceManager persistenceManager = new DefaultPersistenceManager();
+ persistenceManager.initialize(properties);
+ return persistenceManager;
+ }
+}
diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceRealm.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceRealm.java
new file mode 100644
index 000000000..4cc3e82fa
--- /dev/null
+++ b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceRealm.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2013 Robert von Burg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ch.eitchnet.xmlpers.api;
+
+import ch.eitchnet.xmlpers.objref.ObjectReferenceCache;
+
+public interface PersistenceRealm {
+
+ /**
+ * @return the realm
+ */
+ public abstract String getRealmName();
+
+ public abstract PersistenceContextFactoryDelegator getCtxFactoryDelegator();
+
+ /**
+ * @return the objectRefCache
+ */
+ public abstract ObjectReferenceCache getObjectRefCache();
+
+ /**
+ * @return the persistenceManager
+ */
+ public abstract PersistenceManager getPersistenceManager();
+
+}
\ No newline at end of file
diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceTransaction.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceTransaction.java
new file mode 100644
index 000000000..0f2efa7e5
--- /dev/null
+++ b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/PersistenceTransaction.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2013 Robert von Burg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ch.eitchnet.xmlpers.api;
+
+import ch.eitchnet.xmlpers.objref.ObjectReferenceCache;
+
+/**
+ * @author Robert von Burg
+ *
+ */
+public interface PersistenceTransaction extends AutoCloseable {
+
+ /**
+ * Returns the {@link TransactionResult} for this transaction
+ *
+ * @return the {@link TransactionResult}
+ *
+ * @throws IllegalStateException
+ * if the transaction has not yet been closed
+ */
+ public TransactionResult getTransactionResult() throws IllegalStateException;
+
+ /**
+ * @throws IllegalStateException
+ * if a result is already set
+ */
+ public void setTransactionResult(TransactionResult txResult) throws IllegalStateException;
+
+ public void setCloseStrategy(TransactionCloseStrategy closeStrategy);
+
+ public void autoCloseableCommit();
+
+ public void autoCloseableRollback();
+
+ @Override
+ public void close() throws XmlPersistenceException;
+
+ public boolean isOpen();
+
+ public ObjectDao getObjectDao();
+
+ public MetadataDao getMetadataDao();
+
+ public FileDao getFileDao();
+
+ public ObjectReferenceCache getObjectRefCache();
+
+ public PersistenceRealm getRealm();
+
+ public IoMode getIoMode();
+
+ public void setIoMode(IoMode ioMode);
+}
\ No newline at end of file
diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/SaxParser.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/SaxParser.java
new file mode 100644
index 000000000..352b43f33
--- /dev/null
+++ b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/SaxParser.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2013 Robert von Burg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ch.eitchnet.xmlpers.api;
+
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+
+import org.xml.sax.helpers.DefaultHandler;
+
+public interface SaxParser {
+
+ public T getObject();
+
+ public void setObject(T object);
+
+ public DefaultHandler getDefaultHandler();
+
+ public void write(XMLStreamWriter xmlWriter) throws XMLStreamException;
+}
\ No newline at end of file
diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/TransactionCloseStrategy.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/TransactionCloseStrategy.java
new file mode 100644
index 000000000..987a8ffbc
--- /dev/null
+++ b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/TransactionCloseStrategy.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2013 Robert von Burg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ch.eitchnet.xmlpers.api;
+
+public enum TransactionCloseStrategy {
+ COMMIT() {
+ @Override
+ public void close(PersistenceTransaction tx) {
+ tx.autoCloseableCommit();
+ }
+ },
+
+ ROLLBACK() {
+ @Override
+ public void close(PersistenceTransaction tx) {
+ tx.autoCloseableRollback();
+ }
+ };
+
+ /**
+ * @param tx
+ */
+ public void close(PersistenceTransaction tx) {
+ throw new UnsupportedOperationException("Override in enum!"); //$NON-NLS-1$
+ }
+}
\ No newline at end of file
diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/TransactionResult.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/TransactionResult.java
new file mode 100644
index 000000000..dbd72c324
--- /dev/null
+++ b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/TransactionResult.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2013 Robert von Burg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ch.eitchnet.xmlpers.api;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import ch.eitchnet.utils.helper.StringHelper;
+
+public class TransactionResult {
+
+ private String realm;
+ private TransactionState state;
+ private Exception failCause;
+
+ private Date startTime;
+ private long txDuration;
+ private long closeDuration;
+
+ private Map modificationByKey;
+
+ public TransactionResult() {
+ this.state = TransactionState.OPEN;
+ this.modificationByKey = new HashMap<>();
+ }
+
+ /**
+ * @return the realm
+ */
+ public String getRealm() {
+ return this.realm;
+ }
+
+ /**
+ * @param realm
+ * the realm to set
+ */
+ public void setRealm(String realm) {
+ this.realm = realm;
+ }
+
+ /**
+ * @return the state
+ */
+ public TransactionState getState() {
+ return this.state;
+ }
+
+ /**
+ * @param state
+ * the state to set
+ */
+ public void setState(TransactionState state) {
+ this.state = state;
+ }
+
+ /**
+ * The internal exception why the transaction failed
+ *
+ * @return the failCause
+ */
+ public Exception getFailCause() {
+ return this.failCause;
+ }
+
+ /**
+ * @param failCause
+ * the failCause to set
+ */
+ public void setFailCause(Exception failCause) {
+ this.failCause = failCause;
+ }
+
+ /**
+ * Start time of the transaction
+ *
+ * @return the startTime
+ */
+ public Date getStartTime() {
+ return this.startTime;
+ }
+
+ /**
+ * @param startTime
+ * the startTime to set
+ */
+ public void setStartTime(Date startTime) {
+ this.startTime = startTime;
+ }
+
+ /**
+ * The duration the transaction was open in nanoseconds
+ *
+ * @return the txDuration
+ */
+ public long getTxDuration() {
+ return this.txDuration;
+ }
+
+ /**
+ * @param txDuration
+ * the txDuration to set
+ */
+ public void setTxDuration(long txDuration) {
+ this.txDuration = txDuration;
+ }
+
+ /**
+ * The duration the transaction took to close in nanoseconds
+ *
+ * @return the closeDuration
+ */
+ public long getCloseDuration() {
+ return this.closeDuration;
+ }
+
+ /**
+ * @param closeDuration
+ * the closeDuration to set
+ */
+ public void setCloseDuration(long closeDuration) {
+ this.closeDuration = closeDuration;
+ }
+
+ /**
+ * @param modificationByKey
+ * the modificationByKey to set
+ */
+ public void setModificationByKey(Map modificationByKey) {
+ this.modificationByKey = modificationByKey;
+ }
+
+ /**
+ * @return
+ */
+ public Set getKeys() {
+ return this.modificationByKey.keySet();
+ }
+
+ /**
+ * @param key
+ * @return
+ */
+ public ModificationResult getModificationResult(String key) {
+ return this.modificationByKey.get(key);
+ }
+
+ @SuppressWarnings("nls")
+ public String getLogMessage() {
+
+ int nrOfObjects = 0;
+ for (ModificationResult result : this.modificationByKey.values()) {
+ nrOfObjects += result.getCreated().size();
+ nrOfObjects += result.getUpdated().size();
+ nrOfObjects += result.getDeleted().size();
+ }
+
+ StringBuilder sb = new StringBuilder();
+ sb.append("TX for realm ");
+ sb.append(getRealm());
+ switch (this.state) {
+ case OPEN:
+ sb.append(" is still open after ");
+ break;
+ case COMMITTED:
+ sb.append(" was completed after ");
+ break;
+ case ROLLED_BACK:
+ sb.append(" was rolled back after ");
+ break;
+ case FAILED:
+ sb.append(" has failed after ");
+ break;
+ default:
+ sb.append(" is in unhandled state ");
+ sb.append(this.state);
+ sb.append(" after ");
+ }
+
+ sb.append(StringHelper.formatNanoDuration(this.txDuration));
+ sb.append(" with close operation taking ");
+ sb.append(StringHelper.formatNanoDuration(this.closeDuration));
+ sb.append(". ");
+ sb.append(nrOfObjects);
+ sb.append(" objects in ");
+ sb.append(this.modificationByKey.size());
+ sb.append(" types were modified.");
+
+ return sb.toString();
+ }
+
+ /**
+ * Clears all fields of this result, allowing it to be reused
+ */
+ public void clear() {
+ this.realm = null;
+ this.state = null;
+ this.failCause = null;
+ this.startTime = null;
+ this.txDuration = 0L;
+ this.closeDuration = 0L;
+ }
+}
diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/TransactionState.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/TransactionState.java
new file mode 100644
index 000000000..42900e5a4
--- /dev/null
+++ b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/TransactionState.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2013 Robert von Burg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ch.eitchnet.xmlpers.api;
+
+public enum TransactionState {
+ OPEN, //
+ COMMITTED, //
+ ROLLED_BACK, //
+ FAILED;
+}
diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceException.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceException.java
new file mode 100644
index 000000000..258118918
--- /dev/null
+++ b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/api/XmlPersistenceException.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2013 Robert von Burg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ch.eitchnet.xmlpers.api;
+
+/**
+ * @author Robert von Burg
+ */
+public class XmlPersistenceException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * @param message
+ * @param cause
+ */
+ public XmlPersistenceException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ /**
+ * @param message
+ */
+ public XmlPersistenceException(String message) {
+ super(message);
+ }
+}
diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceManager.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceManager.java
new file mode 100644
index 000000000..32eff87f7
--- /dev/null
+++ b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceManager.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2013 Robert von Burg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ch.eitchnet.xmlpers.impl;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.text.MessageFormat;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import ch.eitchnet.utils.helper.PropertiesHelper;
+import ch.eitchnet.utils.helper.StringHelper;
+import ch.eitchnet.xmlpers.api.IoMode;
+import ch.eitchnet.xmlpers.api.PersistenceConstants;
+import ch.eitchnet.xmlpers.api.PersistenceContextFactoryDelegator;
+import ch.eitchnet.xmlpers.api.PersistenceManager;
+import ch.eitchnet.xmlpers.api.PersistenceTransaction;
+import ch.eitchnet.xmlpers.api.XmlPersistenceException;
+import ch.eitchnet.xmlpers.objref.LockableObject;
+import ch.eitchnet.xmlpers.objref.ObjectReferenceCache;
+
+/**
+ * @author Robert von Burg
+ *
+ */
+public class DefaultPersistenceManager implements PersistenceManager {
+
+ protected static final Logger logger = LoggerFactory.getLogger(DefaultPersistenceManager.class);
+
+ protected boolean initialized;
+ protected boolean verbose;
+ protected IoMode defaultIoMode;
+ protected Properties properties;
+ protected Map realmMap;
+ private PersistenceContextFactoryDelegator ctxFactory;
+
+ public void initialize(Properties properties) {
+ if (this.initialized)
+ throw new IllegalStateException("Already initialized!"); //$NON-NLS-1$
+
+ String context = DefaultPersistenceManager.class.getSimpleName();
+
+ // get properties
+ boolean verbose = PropertiesHelper.getPropertyBool(properties, context, PersistenceConstants.PROP_VERBOSE,
+ Boolean.FALSE).booleanValue();
+ String ioModeS = PropertiesHelper.getProperty(properties, context, PersistenceConstants.PROP_XML_IO_MOD,
+ IoMode.DOM.name());
+ IoMode ioMode = IoMode.valueOf(ioModeS);
+ long lockTime = PropertiesHelper.getPropertyLong(properties, context,
+ PersistenceConstants.PROP_LOCK_TIME_MILLIS, 10000L);
+
+ // set lock time on LockableObject
+ try {
+ Field lockTimeField = LockableObject.class.getDeclaredField("tryLockTime");//$NON-NLS-1$
+ lockTimeField.setAccessible(true);
+ lockTimeField.setLong(null, lockTime);
+ logger.info("Using a max lock acquire time of " + StringHelper.formatMillisecondsDuration(lockTime)); //$NON-NLS-1$
+ } catch (SecurityException | NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) {
+ throw new RuntimeException("Failed to configure tryLockTime on LockableObject!", e); //$NON-NLS-1$
+ }
+
+ // validate base path
+ validateBasePath(properties);
+
+ this.properties = properties;
+ this.verbose = verbose;
+ this.defaultIoMode = ioMode;
+ this.realmMap = new HashMap<>();
+ this.ctxFactory = new PersistenceContextFactoryDelegator();
+ }
+
+ private void validateBasePath(Properties properties) {
+ String context = DefaultPersistenceManager.class.getSimpleName();
+ String basePath = PropertiesHelper.getProperty(properties, context, PersistenceConstants.PROP_BASEPATH, null);
+
+ // validate base path exists and is writable
+ File basePathF = new File(basePath);
+ if (!basePathF.exists())
+ throw new XmlPersistenceException(MessageFormat.format("The database store path does not exist at {0}", //$NON-NLS-1$
+ basePathF.getAbsolutePath()));
+ if (!basePathF.canWrite())
+ throw new XmlPersistenceException(MessageFormat.format("The database store path is not writeable at {0}", //$NON-NLS-1$
+ basePathF.getAbsolutePath()));
+ }
+
+ @Override
+ public PersistenceContextFactoryDelegator getCtxFactory() {
+ return this.ctxFactory;
+ }
+
+ @Override
+ public PersistenceTransaction openTx() {
+ return openTx(DEFAULT_REALM);
+ }
+
+ @Override
+ public synchronized PersistenceTransaction openTx(String realmName) {
+
+ DefaultPersistenceRealm persistenceRealm = this.realmMap.get(realmName);
+ if (persistenceRealm == null) {
+
+ PathBuilder pathBuilder = new PathBuilder(realmName, this.properties);
+ ObjectReferenceCache objectRefCache = new ObjectReferenceCache(realmName);
+ persistenceRealm = new DefaultPersistenceRealm(realmName, this, this.ctxFactory, pathBuilder,
+ objectRefCache);
+
+ this.realmMap.put(realmName, persistenceRealm);
+ }
+
+ PersistenceTransaction tx = new DefaultPersistenceTransaction(persistenceRealm, this.verbose);
+ tx.setIoMode(this.defaultIoMode);
+ return tx;
+ }
+}
diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceRealm.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceRealm.java
new file mode 100644
index 000000000..547ffc262
--- /dev/null
+++ b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceRealm.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2013 Robert von Burg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ch.eitchnet.xmlpers.impl;
+
+import ch.eitchnet.xmlpers.api.PersistenceContextFactoryDelegator;
+import ch.eitchnet.xmlpers.api.PersistenceManager;
+import ch.eitchnet.xmlpers.api.PersistenceRealm;
+import ch.eitchnet.xmlpers.objref.ObjectReferenceCache;
+
+public class DefaultPersistenceRealm implements PersistenceRealm {
+ private final PersistenceManager persistenceManager;
+ private final PersistenceContextFactoryDelegator ctxFactory;
+ private final String realmName;
+ private final ObjectReferenceCache objectRefCache;
+ private final PathBuilder pathBuilder;
+
+ public DefaultPersistenceRealm(String realm, PersistenceManager persistenceManager,
+ PersistenceContextFactoryDelegator ctxFactory, PathBuilder pathBuilder, ObjectReferenceCache objectRefCache) {
+ this.ctxFactory = ctxFactory;
+ this.pathBuilder = pathBuilder;
+ this.realmName = realm;
+ this.persistenceManager = persistenceManager;
+ this.objectRefCache = objectRefCache;
+ }
+
+ /**
+ * @return the realm
+ */
+ @Override
+ public String getRealmName() {
+ return this.realmName;
+ }
+
+ @Override
+ public PersistenceContextFactoryDelegator getCtxFactoryDelegator() {
+ return this.ctxFactory;
+ }
+
+ /**
+ * @return the objectRefCache
+ */
+ @Override
+ public ObjectReferenceCache getObjectRefCache() {
+ return this.objectRefCache;
+ }
+
+ /**
+ * @return the persistenceManager
+ */
+ @Override
+ public PersistenceManager getPersistenceManager() {
+ return this.persistenceManager;
+ }
+
+ /**
+ * @return the pathBuilder
+ */
+ PathBuilder getPathBuilder() {
+ return this.pathBuilder;
+ }
+}
\ No newline at end of file
diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java
new file mode 100644
index 000000000..1590aedba
--- /dev/null
+++ b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/DefaultPersistenceTransaction.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright 2013 Robert von Burg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ch.eitchnet.xmlpers.impl;
+
+import java.text.MessageFormat;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import ch.eitchnet.utils.helper.StringHelper;
+import ch.eitchnet.utils.objectfilter.ObjectFilter;
+import ch.eitchnet.xmlpers.api.FileDao;
+import ch.eitchnet.xmlpers.api.IoMode;
+import ch.eitchnet.xmlpers.api.MetadataDao;
+import ch.eitchnet.xmlpers.api.ModificationResult;
+import ch.eitchnet.xmlpers.api.ObjectDao;
+import ch.eitchnet.xmlpers.api.PersistenceContext;
+import ch.eitchnet.xmlpers.api.PersistenceRealm;
+import ch.eitchnet.xmlpers.api.PersistenceTransaction;
+import ch.eitchnet.xmlpers.api.TransactionCloseStrategy;
+import ch.eitchnet.xmlpers.api.TransactionResult;
+import ch.eitchnet.xmlpers.api.TransactionState;
+import ch.eitchnet.xmlpers.api.XmlPersistenceException;
+import ch.eitchnet.xmlpers.objref.ObjectReferenceCache;
+
+/**
+ * @author Robert von Burg
+ *
+ */
+public class DefaultPersistenceTransaction implements PersistenceTransaction {
+
+ private static final Logger logger = LoggerFactory.getLogger(DefaultPersistenceTransaction.class);
+
+ private final DefaultPersistenceRealm realm;
+ private final boolean verbose;
+
+ private final ObjectFilter objectFilter;
+ private final ObjectDao objectDao;
+ private final MetadataDao metadataDao;
+
+ private FileDao fileDao;
+ private IoMode ioMode;
+
+ private TransactionCloseStrategy closeStrategy;
+
+ private TransactionState state;
+ private long startTime;
+ private Date startTimeDate;
+ private TransactionResult txResult;
+
+ public DefaultPersistenceTransaction(DefaultPersistenceRealm realm, boolean verbose) {
+ this.startTime = System.nanoTime();
+ this.startTimeDate = new Date();
+ this.realm = realm;
+ this.verbose = verbose;
+ this.objectFilter = new ObjectFilter();
+ this.fileDao = new FileDao(this, realm.getPathBuilder(), verbose);
+ this.objectDao = new ObjectDao(this, this.fileDao, this.objectFilter);
+ this.metadataDao = new MetadataDao(realm.getPathBuilder(), this, verbose);
+
+ this.closeStrategy = TransactionCloseStrategy.COMMIT;
+ this.state = TransactionState.OPEN;
+ }
+
+ @Override
+ public void setTransactionResult(TransactionResult txResult) throws IllegalStateException {
+ if (this.txResult != null) {
+ String msg = "The transaction already has a result set!"; //$NON-NLS-1$
+ throw new IllegalStateException(msg);
+ }
+ this.txResult = txResult;
+ }
+
+ @Override
+ public TransactionResult getTransactionResult() throws IllegalStateException {
+ if (isOpen()) {
+ String msg = "The transaction is still open thus has no result yet! Either commit or rollback before calling this method"; //$NON-NLS-1$
+ throw new IllegalStateException(msg);
+ }
+ return this.txResult;
+ }
+
+ @Override
+ public PersistenceRealm getRealm() {
+ return this.realm;
+ }
+
+ @Override
+ public ObjectDao getObjectDao() {
+ return this.objectDao;
+ }
+
+ @Override
+ public MetadataDao getMetadataDao() {
+ return this.metadataDao;
+ }
+
+ @Override
+ public FileDao getFileDao() {
+ return this.fileDao;
+ }
+
+ @Override
+ public ObjectReferenceCache getObjectRefCache() {
+ return this.realm.getObjectRefCache();
+ }
+
+ @Override
+ public void setCloseStrategy(TransactionCloseStrategy closeStrategy) {
+ this.closeStrategy = closeStrategy;
+ }
+
+ @Override
+ public void close() throws XmlPersistenceException {
+ this.closeStrategy.close(this);
+ }
+
+ @Override
+ public void autoCloseableRollback() {
+ long start = System.nanoTime();
+ if (this.state == TransactionState.COMMITTED)
+ throw new IllegalStateException("Transaction has already been committed!"); //$NON-NLS-1$
+
+ if (this.state != TransactionState.ROLLED_BACK) {
+ unlockObjectRefs();
+ this.state = TransactionState.ROLLED_BACK;
+ this.objectFilter.clearCache();
+
+ long end = System.nanoTime();
+ long txDuration = end - this.startTime;
+ long closeDuration = end - start;
+
+ if (this.txResult != null) {
+ this.txResult.clear();
+ this.txResult.setState(this.state);
+ this.txResult.setStartTime(this.startTimeDate);
+ this.txResult.setTxDuration(txDuration);
+ this.txResult.setCloseDuration(closeDuration);
+ this.txResult.setRealm(this.realm.getRealmName());
+ this.txResult.setModificationByKey(Collections. emptyMap());
+ }
+ }
+ }
+
+ @Override
+ public void autoCloseableCommit() throws XmlPersistenceException {
+
+ long start = System.nanoTime();
+
+ try {
+
+ if (this.verbose) {
+ String msg = "Committing {0} operations in TX...";//$NON-NLS-1$
+ logger.info(MessageFormat.format(msg, this.objectFilter.sizeCache()));
+ }
+
+ Set keySet = this.objectFilter.keySet();
+ Map modifications;
+ if (this.txResult == null)
+ modifications = null;
+ else
+ modifications = new HashMap<>(keySet.size());
+
+ for (String key : keySet) {
+
+ List