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 removed = this.objectFilter.getRemoved(key); + if (removed.isEmpty()) { + if (this.verbose) + logger.info("No objects removed in this tx."); //$NON-NLS-1$ + } else { + if (this.verbose) + logger.info(removed.size() + " objects removed in this tx."); //$NON-NLS-1$ + + for (Object object : removed) { + @SuppressWarnings("unchecked") + PersistenceContext ctx = (PersistenceContext) object; + this.fileDao.performDelete(ctx); + } + } + + List updated = this.objectFilter.getUpdated(key); + if (updated.isEmpty()) { + if (this.verbose) + logger.info("No objects updated in this tx."); //$NON-NLS-1$ + } else { + if (this.verbose) + logger.info(updated.size() + " objects updated in this tx."); //$NON-NLS-1$ + + for (Object object : updated) { + @SuppressWarnings("unchecked") + PersistenceContext ctx = (PersistenceContext) object; + this.fileDao.performUpdate(ctx); + } + } + + List added = this.objectFilter.getAdded(key); + if (added.isEmpty()) { + if (this.verbose) + logger.info("No objects added in this tx."); //$NON-NLS-1$ + } else { + if (this.verbose) + logger.info(added.size() + " objects added in this tx."); //$NON-NLS-1$ + + for (Object object : added) { + @SuppressWarnings("unchecked") + PersistenceContext ctx = (PersistenceContext) object; + this.fileDao.performCreate(ctx); + } + } + + if (modifications != null) { + ModificationResult result = new ModificationResult(key, added, updated, removed); + modifications.put(key, result); + } + } + + if (this.txResult != null) { + this.txResult.clear(); + this.txResult.setState(TransactionState.COMMITTED); + this.txResult.setModificationByKey(modifications); + } + + } catch (Exception e) { + + if (this.txResult == null) { + + long end = System.nanoTime(); + long txDuration = end - this.startTime; + long closeDuration = end - start; + + StringBuilder sb = new StringBuilder(); + sb.append("TX has failed after "); //$NON-NLS-1$ + sb.append(StringHelper.formatNanoDuration(txDuration)); + sb.append(" with close operation taking "); //$NON-NLS-1$ + sb.append(StringHelper.formatNanoDuration(closeDuration)); + logger.info(sb.toString()); + + throw e; + } + + this.txResult.clear(); + this.txResult.setState(TransactionState.FAILED); + this.txResult.setModificationByKey(Collections. emptyMap()); + this.txResult.setFailCause(e); + + } finally { + + // clean up + unlockObjectRefs(); + this.objectFilter.clearCache(); + } + + long end = System.nanoTime(); + long txDuration = end - this.startTime; + long closeDuration = end - start; + + if (this.txResult == null) { + + StringBuilder sb = new StringBuilder(); + sb.append("TX was completed after "); //$NON-NLS-1$ + sb.append(StringHelper.formatNanoDuration(txDuration)); + sb.append(" with close operation taking "); //$NON-NLS-1$ + sb.append(StringHelper.formatNanoDuration(closeDuration)); + logger.info(sb.toString()); + + } else { + + this.txResult.setStartTime(this.startTimeDate); + this.txResult.setTxDuration(txDuration); + this.txResult.setCloseDuration(closeDuration); + this.txResult.setRealm(this.realm.getRealmName()); + + if (this.txResult.getState() == TransactionState.FAILED) { + String msg = "Failed to commit TX due to underlying exception: {0}"; //$NON-NLS-1$ + String causeMsg = this.txResult.getFailCause() == null ? null : this.txResult.getFailCause() + .getMessage(); + msg = MessageFormat.format(msg, causeMsg); + throw new XmlPersistenceException(msg, this.txResult.getFailCause()); + } + } + } + + @SuppressWarnings("rawtypes") + private void unlockObjectRefs() { + List lockedObjects = this.objectFilter.getAll(PersistenceContext.class); + for (PersistenceContext lockedObject : lockedObjects) { + lockedObject.getObjectRef().unlock(); + } + } + + @Override + public boolean isOpen() { + return this.state == TransactionState.OPEN; + } + + @Override + public void setIoMode(IoMode ioMode) { + this.ioMode = ioMode; + } + + @Override + public IoMode getIoMode() { + return this.ioMode; + } +} diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/PathBuilder.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/PathBuilder.java new file mode 100644 index 000000000..8b0d582a3 --- /dev/null +++ b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/impl/PathBuilder.java @@ -0,0 +1,123 @@ +/* + * 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.io.IOException; +import java.text.MessageFormat; +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.PersistenceConstants; +import ch.eitchnet.xmlpers.api.XmlPersistenceException; + +/** + * @author Robert von Burg + */ +public class PathBuilder { + + private static final Logger logger = LoggerFactory.getLogger(PathBuilder.class); + + public static final String FILE_EXT = ".xml"; //$NON-NLS-1$ + public static final int EXT_LENGTH = PathBuilder.FILE_EXT.length(); + + private final String basePath; + + public PathBuilder(String realm, Properties properties) { + + // get properties + String context = PathBuilder.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())); + + File realmPathF = new File(basePathF, realm); + if (!realmPathF.exists() && !realmPathF.mkdir()) + throw new XmlPersistenceException(MessageFormat.format("Could not create path for realm {0} at {1}", //$NON-NLS-1$ + realm, basePathF.getAbsolutePath())); + + // we want a clean base path + String canonicalBasePath; + try { + canonicalBasePath = realmPathF.getCanonicalPath(); + } catch (IOException e) { + throw new XmlPersistenceException(MessageFormat.format( + "Failed to build canonical path from {0}", realmPathF.getAbsolutePath()), e); //$NON-NLS-1$ + } + + // this.basePathF = basePathF; + this.basePath = canonicalBasePath; + logger.info(MessageFormat.format("Using base path {0}", this.basePath)); //$NON-NLS-1$ + } + + String getFilename(String id) { + return id.concat(PathBuilder.FILE_EXT); + } + + String getPathAsString(String type, String subType, String id) { + StringBuilder sb = new StringBuilder(this.basePath); + if (StringHelper.isNotEmpty(type)) { + sb.append(File.separatorChar); + sb.append(type); + } + if (StringHelper.isNotEmpty(subType)) { + sb.append(File.separatorChar); + sb.append(subType); + } + if (StringHelper.isNotEmpty(id)) { + sb.append(File.separatorChar); + sb.append(getFilename(id)); + } + + return sb.toString(); + } + + public File getRootPath() { + File path = new File(getPathAsString(null, null, null)); + return path; + } + + public File getTypePath(String type) { + File path = new File(getPathAsString(type, null, null)); + return path; + } + + public File getSubTypePath(String type, String subType) { + File path = new File(getPathAsString(type, subType, null)); + return path; + } + + public File getIdOfTypePath(String type, String id) { + File path = new File(getPathAsString(type, null, id)); + return path; + } + + public File getIdOfSubTypePath(String type, String subType, String id) { + File path = new File(getPathAsString(type, subType, id)); + return path; + } +} diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/IdOfSubTypeRef.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/IdOfSubTypeRef.java new file mode 100644 index 000000000..bb4d206c1 --- /dev/null +++ b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/IdOfSubTypeRef.java @@ -0,0 +1,137 @@ +/* + * 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.objref; + +import java.io.File; +import java.text.MessageFormat; + +import ch.eitchnet.xmlpers.api.PersistenceContext; +import ch.eitchnet.xmlpers.api.PersistenceContextFactory; +import ch.eitchnet.xmlpers.api.PersistenceContextFactoryDelegator; +import ch.eitchnet.xmlpers.api.PersistenceTransaction; +import ch.eitchnet.xmlpers.impl.PathBuilder; + +public class IdOfSubTypeRef extends ObjectRef { + + private final String type; + private final String subType; + private final String id; + + public IdOfSubTypeRef(String realmName, String type, String subType, String id) { + super(realmName, RefNameCreator.createIdOfSubTypeName(realmName, type, subType, id)); + this.type = type; + this.subType = subType; + this.id = id; + } + + @Override + public String getType() { + return this.type; + } + + public String getSubType() { + return this.subType; + } + + /** + * @return the id + */ + public String getId() { + return this.id; + } + + @Override + public boolean isRoot() { + return false; + } + + @Override + public boolean isLeaf() { + return true; + } + + @Override + public ObjectRef getParent(PersistenceTransaction tx) { + return tx.getRealm().getObjectRefCache().getSubTypeRef(this.type, this.subType); + } + + @Override + public ObjectRef getChildIdRef(PersistenceTransaction tx, String id) { + String msg = MessageFormat.format("Already a leaf: {0}", getName()); //$NON-NLS-1$ + throw new UnsupportedOperationException(msg); + } + + @Override + public ObjectRef getChildTypeRef(PersistenceTransaction tx, String type) { + String msg = MessageFormat.format("Already a leaf: {0}", getName()); //$NON-NLS-1$ + throw new UnsupportedOperationException(msg); + } + + @Override + public File getPath(PathBuilder pathBuilder) { + return pathBuilder.getIdOfSubTypePath(this.type, this.subType, this.id); + } + + @Override + public PersistenceContext createPersistenceContext(PersistenceTransaction tx) { + PersistenceContextFactoryDelegator ctxFactoryDelegator = tx.getRealm().getCtxFactoryDelegator(); + PersistenceContextFactory persistenceContextFactory = ctxFactoryDelegator. getCtxFactory(this.type); + return persistenceContextFactory.createCtx(this); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((this.id == null) ? 0 : this.id.hashCode()); + result = prime * result + ((this.realmName == null) ? 0 : this.realmName.hashCode()); + result = prime * result + ((this.subType == null) ? 0 : this.subType.hashCode()); + result = prime * result + ((this.type == null) ? 0 : this.type.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; + IdOfSubTypeRef other = (IdOfSubTypeRef) obj; + if (this.realmName == null) { + if (other.realmName != null) + return false; + } else if (!this.realmName.equals(other.realmName)) + return false; + if (this.id == null) { + if (other.id != null) + return false; + } else if (!this.id.equals(other.id)) + return false; + if (this.subType == null) { + if (other.subType != null) + return false; + } else if (!this.subType.equals(other.subType)) + return false; + if (this.type == null) { + if (other.type != null) + return false; + } else if (!this.type.equals(other.type)) + return false; + return true; + } +} diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/IdOfTypeRef.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/IdOfTypeRef.java new file mode 100644 index 000000000..a110df9b1 --- /dev/null +++ b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/IdOfTypeRef.java @@ -0,0 +1,125 @@ +/* + * 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.objref; + +import java.io.File; +import java.text.MessageFormat; + +import ch.eitchnet.xmlpers.api.PersistenceContext; +import ch.eitchnet.xmlpers.api.PersistenceContextFactory; +import ch.eitchnet.xmlpers.api.PersistenceContextFactoryDelegator; +import ch.eitchnet.xmlpers.api.PersistenceTransaction; +import ch.eitchnet.xmlpers.impl.PathBuilder; + +public class IdOfTypeRef extends ObjectRef { + + private final String type; + private final String id; + + public IdOfTypeRef(String realmName, String type, String id) { + super(realmName, RefNameCreator.createIdOfTypeName(realmName, type, id)); + this.type = type; + this.id = id; + } + + @Override + public String getType() { + return this.type; + } + + /** + * @return the id + */ + public String getId() { + return this.id; + } + + @Override + public boolean isRoot() { + return false; + } + + @Override + public boolean isLeaf() { + return true; + } + + @Override + public ObjectRef getParent(PersistenceTransaction tx) { + return tx.getRealm().getObjectRefCache().getTypeRef(this.type); + } + + @Override + public ObjectRef getChildIdRef(PersistenceTransaction tx, String id) { + String msg = MessageFormat.format("Already a leaf: {0}", getName()); //$NON-NLS-1$ + throw new UnsupportedOperationException(msg); + } + + @Override + public ObjectRef getChildTypeRef(PersistenceTransaction tx, String type) { + String msg = MessageFormat.format("Already a leaf: {0}", getName()); //$NON-NLS-1$ + throw new UnsupportedOperationException(msg); + } + + @Override + public File getPath(PathBuilder pathBuilder) { + return pathBuilder.getIdOfTypePath(this.type, this.id); + } + + @Override + public PersistenceContext createPersistenceContext(PersistenceTransaction tx) { + PersistenceContextFactoryDelegator ctxFactoryDelegator = tx.getRealm().getCtxFactoryDelegator(); + PersistenceContextFactory persistenceContextFactory = ctxFactoryDelegator. getCtxFactory(this.type); + return persistenceContextFactory.createCtx(this); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((this.id == null) ? 0 : this.id.hashCode()); + result = prime * result + ((this.realmName == null) ? 0 : this.realmName.hashCode()); + result = prime * result + ((this.type == null) ? 0 : this.type.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; + IdOfTypeRef other = (IdOfTypeRef) obj; + if (this.realmName == null) { + if (other.realmName != null) + return false; + } else if (!this.realmName.equals(other.realmName)) + return false; + if (this.id == null) { + if (other.id != null) + return false; + } else if (!this.id.equals(other.id)) + return false; + if (this.type == null) { + if (other.type != null) + return false; + } else if (!this.type.equals(other.type)) + return false; + return true; + } +} diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java new file mode 100644 index 000000000..545de62d7 --- /dev/null +++ b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/LockableObject.java @@ -0,0 +1,69 @@ +/* + * 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.objref; + +import java.text.MessageFormat; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.utils.helper.StringHelper; +import ch.eitchnet.xmlpers.api.XmlPersistenceException; + +public class LockableObject { + + private static final Logger logger = LoggerFactory.getLogger(LockableObject.class); + private static long tryLockTime = 10000L; + + private final ReentrantLock lock; + + public LockableObject() { + this.lock = new ReentrantLock(true); + } + + public static long getLockTime() { + return tryLockTime; + } + + /** + * @see java.util.concurrent.locks.ReentrantLock#tryLock(long, TimeUnit) + */ + public void lock() { + try { + + if (!this.lock.tryLock(tryLockTime, TimeUnit.MILLISECONDS)) { + String msg = "Failed to acquire lock after {0} for {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, StringHelper.formatMillisecondsDuration(tryLockTime), toString()); + throw new XmlPersistenceException(msg); + } + if (logger.isDebugEnabled()) + logger.debug("locked " + toString()); //$NON-NLS-1$ + } catch (InterruptedException e) { + throw new XmlPersistenceException("Thread interrupted: " + e.getMessage(), e); //$NON-NLS-1$ + } + } + + /** + * @see java.util.concurrent.locks.ReentrantLock#unlock() + */ + public void unlock() { + this.lock.unlock(); + if (logger.isDebugEnabled()) + logger.debug("unlocking " + toString()); //$NON-NLS-1$ + } +} diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/ObjectRef.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/ObjectRef.java new file mode 100644 index 000000000..bc6810914 --- /dev/null +++ b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/ObjectRef.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.objref; + +import java.io.File; + +import ch.eitchnet.xmlpers.api.PersistenceContext; +import ch.eitchnet.xmlpers.api.PersistenceTransaction; +import ch.eitchnet.xmlpers.impl.PathBuilder; + +public abstract class ObjectRef extends LockableObject { + + protected final String realmName; + protected final String name; + + protected ObjectRef(String realmName, String name) { + this.realmName = realmName; + this.name = name; + } + + public String getRealmName() { + return this.realmName; + } + + public String getName() { + return this.name; + } + + public abstract boolean isRoot(); + + public abstract boolean isLeaf(); + + public abstract String getType(); + + public abstract ObjectRef getParent(PersistenceTransaction tx); + + public abstract ObjectRef getChildIdRef(PersistenceTransaction tx, String id); + + public abstract ObjectRef getChildTypeRef(PersistenceTransaction tx, String type); + + public abstract File getPath(PathBuilder pathBuilder); + + public abstract PersistenceContext createPersistenceContext(PersistenceTransaction tx); + + @Override + public String toString() { + return getName(); + } + + @Override + public abstract boolean equals(Object obj); + + @Override + public abstract int hashCode(); +} diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/ObjectReferenceCache.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/ObjectReferenceCache.java new file mode 100644 index 000000000..67fd55562 --- /dev/null +++ b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/ObjectReferenceCache.java @@ -0,0 +1,95 @@ +/* + * 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.objref; + +import java.util.HashMap; +import java.util.Map; + +import ch.eitchnet.utils.helper.StringHelper; + +public class ObjectReferenceCache { + + private final String realmName; + private final RootRef rootRef; + private final Map typeRefMap; + private final Map subTypeRefMap; + private final Map idOfTypeRefMap; + private final Map idOfSubTypeRefMap; + + public ObjectReferenceCache(String realmName) { + if (StringHelper.isEmpty(realmName)) + throw new IllegalArgumentException("Realm name may not be empty!"); //$NON-NLS-1$ + + this.realmName = realmName; + this.rootRef = new RootRef(this.realmName); + this.typeRefMap = new HashMap<>(); + this.subTypeRefMap = new HashMap<>(); + this.idOfTypeRefMap = new HashMap<>(); + this.idOfSubTypeRefMap = new HashMap<>(); + } + + public String getRealmName() { + return this.realmName; + } + + public synchronized RootRef getRootRef() { + return this.rootRef; + } + + public synchronized TypeRef getTypeRef(String type) { + String key = RefNameCreator.createTypeName(this.realmName, type); + TypeRef ref = this.typeRefMap.get(key); + if (ref == null) { + ref = new TypeRef(this.realmName, type); + this.typeRefMap.put(key, ref); + } + + return ref; + } + + public synchronized SubTypeRef getSubTypeRef(String type, String subType) { + String key = RefNameCreator.createSubTypeName(this.realmName, type, subType); + SubTypeRef ref = this.subTypeRefMap.get(key); + if (ref == null) { + ref = new SubTypeRef(this.realmName, type, subType); + this.subTypeRefMap.put(key, ref); + } + + return ref; + } + + public synchronized IdOfTypeRef getIdOfTypeRef(String type, String id) { + String key = RefNameCreator.createIdOfTypeName(this.realmName, type, id); + IdOfTypeRef idRef = this.idOfTypeRefMap.get(key); + if (idRef == null) { + idRef = new IdOfTypeRef(this.realmName, type, id); + this.idOfTypeRefMap.put(key, idRef); + } + + return idRef; + } + + public synchronized IdOfSubTypeRef getIdOfSubTypeRef(String type, String subType, String id) { + String key = RefNameCreator.createIdOfSubTypeName(this.realmName, type, subType, id); + IdOfSubTypeRef idRef = this.idOfSubTypeRefMap.get(key); + if (idRef == null) { + idRef = new IdOfSubTypeRef(this.realmName, type, subType, id); + this.idOfSubTypeRefMap.put(key, idRef); + } + + return idRef; + } +} diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/RefNameCreator.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/RefNameCreator.java new file mode 100644 index 000000000..d408c758d --- /dev/null +++ b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/RefNameCreator.java @@ -0,0 +1,82 @@ +/* + * 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.objref; + +import ch.eitchnet.utils.helper.StringHelper; + +public class RefNameCreator { + + protected static final String SLASH = "/"; //$NON-NLS-1$ + + // FIXME validate each name part that it is a valid literal for file names... + + public static String createRootName(String realmName) { + assertRealmName(realmName); + return SLASH + realmName + SLASH; + } + + public static String createTypeName(String realmName, String type) { + assertRealmName(realmName); + assertType(type); + return SLASH + realmName + SLASH + type + SLASH; + } + + public static String createIdOfTypeName(String realmName, String type, String id) { + assertRealmName(realmName); + assertType(type); + assertId(id); + return SLASH + realmName + SLASH + type + SLASH + id; + } + + public static String createSubTypeName(String realmName, String type, String subType) { + assertRealmName(realmName); + assertType(type); + assertSubType(subType); + return SLASH + realmName + SLASH + type + SLASH + subType + SLASH; + } + + public static String createIdOfSubTypeName(String realmName, String type, String subType, String id) { + assertRealmName(realmName); + assertType(type); + assertSubType(subType); + assertId(id); + return SLASH + realmName + SLASH + type + SLASH + subType + SLASH + id; + } + + private static void assertRealmName(String realmName) { + if (StringHelper.isEmpty(realmName)) { + throw new IllegalArgumentException("Realm name may not be empty!"); //$NON-NLS-1$ + } + } + + private static void assertType(String type) { + if (StringHelper.isEmpty(type)) { + throw new IllegalArgumentException("type may not be empty!"); //$NON-NLS-1$ + } + } + + private static void assertSubType(String subType) { + if (StringHelper.isEmpty(subType)) { + throw new IllegalArgumentException("subType may not be empty!"); //$NON-NLS-1$ + } + } + + private static void assertId(String id) { + if (StringHelper.isEmpty(id)) { + throw new IllegalArgumentException("id may not be empty!"); //$NON-NLS-1$ + } + } +} diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/RootRef.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/RootRef.java new file mode 100644 index 000000000..3776544c9 --- /dev/null +++ b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/RootRef.java @@ -0,0 +1,99 @@ +/* + * 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.objref; + +import java.io.File; +import java.text.MessageFormat; + +import ch.eitchnet.xmlpers.api.PersistenceContext; +import ch.eitchnet.xmlpers.api.PersistenceTransaction; +import ch.eitchnet.xmlpers.impl.PathBuilder; + +public class RootRef extends ObjectRef { + + public RootRef(String realmName) { + super(realmName, RefNameCreator.createRootName(realmName)); + } + + @Override + public boolean isRoot() { + return true; + } + + @Override + public boolean isLeaf() { + return false; + } + + @Override + public String getType() { + String msg = MessageFormat.format("RootRef has no type: {0}", getName()); //$NON-NLS-1$ + throw new UnsupportedOperationException(msg); + } + + @Override + public ObjectRef getParent(PersistenceTransaction tx) { + String msg = MessageFormat.format("RootRef has no parent: {0}", getName()); //$NON-NLS-1$ + throw new UnsupportedOperationException(msg); + } + + @Override + public ObjectRef getChildIdRef(PersistenceTransaction tx, String id) { + String msg = MessageFormat.format("RootRef has no child id: {0}", getName()); //$NON-NLS-1$ + throw new UnsupportedOperationException(msg); + } + + @Override + public ObjectRef getChildTypeRef(PersistenceTransaction tx, String type) { + return tx.getRealm().getObjectRefCache().getTypeRef(type); + } + + @Override + public File getPath(PathBuilder pathBuilder) { + return pathBuilder.getRootPath(); + } + + @Override + public PersistenceContext createPersistenceContext(PersistenceTransaction tx) { + String msg = MessageFormat.format("{0} is not a leaf and can thus not have a Persistence Context", getName()); //$NON-NLS-1$ + throw new UnsupportedOperationException(msg); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((this.realmName == null) ? 0 : this.realmName.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; + RootRef other = (RootRef) obj; + if (this.realmName == null) { + if (other.realmName != null) + return false; + } else if (!this.realmName.equals(other.realmName)) + return false; + return true; + } +} diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/SubTypeRef.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/SubTypeRef.java new file mode 100644 index 000000000..ed792d5f5 --- /dev/null +++ b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/SubTypeRef.java @@ -0,0 +1,118 @@ +/* + * 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.objref; + +import java.io.File; +import java.text.MessageFormat; + +import ch.eitchnet.xmlpers.api.PersistenceContext; +import ch.eitchnet.xmlpers.api.PersistenceTransaction; +import ch.eitchnet.xmlpers.impl.PathBuilder; + +public class SubTypeRef extends ObjectRef { + + private final String type; + private final String subType; + + public SubTypeRef(String realmName, String type, String subType) { + super(realmName, RefNameCreator.createSubTypeName(realmName, type, subType)); + this.type = type; + this.subType = subType; + } + + @Override + public String getType() { + return this.type; + } + + public String getSubType() { + return this.subType; + } + + @Override + public boolean isRoot() { + return false; + } + + @Override + public boolean isLeaf() { + return false; + } + + @Override + public ObjectRef getParent(PersistenceTransaction tx) { + return tx.getRealm().getObjectRefCache().getTypeRef(this.type); + } + + @Override + public ObjectRef getChildIdRef(PersistenceTransaction tx, String id) { + return tx.getRealm().getObjectRefCache().getIdOfSubTypeRef(this.type, this.subType, id); + } + + @Override + public ObjectRef getChildTypeRef(PersistenceTransaction tx, String type) { + String msg = MessageFormat.format("A SubType has no child type: {0}", getName()); //$NON-NLS-1$ + throw new UnsupportedOperationException(msg); + } + + @Override + public File getPath(PathBuilder pathBuilder) { + return pathBuilder.getSubTypePath(this.type, this.subType); + } + + @Override + public PersistenceContext createPersistenceContext(PersistenceTransaction tx) { + String msg = MessageFormat.format("{0} is not a leaf and can thus not have a Persistence Context", getName()); //$NON-NLS-1$ + throw new UnsupportedOperationException(msg); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((this.realmName == null) ? 0 : this.realmName.hashCode()); + result = prime * result + ((this.subType == null) ? 0 : this.subType.hashCode()); + result = prime * result + ((this.type == null) ? 0 : this.type.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; + SubTypeRef other = (SubTypeRef) obj; + if (this.realmName == null) { + if (other.realmName != null) + return false; + } else if (!this.realmName.equals(other.realmName)) + return false; + if (this.subType == null) { + if (other.subType != null) + return false; + } else if (!this.subType.equals(other.subType)) + return false; + if (this.type == null) { + if (other.type != null) + return false; + } else if (!this.type.equals(other.type)) + return false; + return true; + } +} diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/TypeRef.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/TypeRef.java new file mode 100644 index 000000000..738a2cff7 --- /dev/null +++ b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/objref/TypeRef.java @@ -0,0 +1,105 @@ +/* + * 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.objref; + +import java.io.File; +import java.text.MessageFormat; + +import ch.eitchnet.xmlpers.api.PersistenceContext; +import ch.eitchnet.xmlpers.api.PersistenceTransaction; +import ch.eitchnet.xmlpers.impl.PathBuilder; + +public class TypeRef extends ObjectRef { + + private final String type; + + public TypeRef(String realmName, String type) { + super(realmName, RefNameCreator.createTypeName(realmName, type)); + this.type = type; + } + + @Override + public String getType() { + return this.type; + } + + @Override + public boolean isRoot() { + return false; + } + + @Override + public boolean isLeaf() { + return false; + } + + @Override + public ObjectRef getParent(PersistenceTransaction tx) { + return tx.getRealm().getObjectRefCache().getRootRef(); + } + + @Override + public ObjectRef getChildIdRef(PersistenceTransaction tx, String id) { + return tx.getRealm().getObjectRefCache().getIdOfTypeRef(this.type, id); + } + + @Override + public ObjectRef getChildTypeRef(PersistenceTransaction tx, String type) { + return tx.getRealm().getObjectRefCache().getSubTypeRef(this.type, type); + } + + @Override + public File getPath(PathBuilder pathBuilder) { + return pathBuilder.getTypePath(this.type); + } + + @Override + public PersistenceContext createPersistenceContext(PersistenceTransaction tx) { + String msg = MessageFormat.format("{0} is not a leaf and can thus not have a Persistence Context", getName()); //$NON-NLS-1$ + throw new UnsupportedOperationException(msg); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((this.realmName == null) ? 0 : this.realmName.hashCode()); + result = prime * result + ((this.type == null) ? 0 : this.type.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; + TypeRef other = (TypeRef) obj; + if (this.realmName == null) { + if (other.realmName != null) + return false; + } else if (!this.realmName.equals(other.realmName)) + return false; + if (this.type == null) { + if (other.type != null) + return false; + } else if (!this.type.equals(other.type)) + return false; + return true; + } +} diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/util/AssertionUtil.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/util/AssertionUtil.java new file mode 100644 index 000000000..6a3c9535a --- /dev/null +++ b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/util/AssertionUtil.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.util; + +import java.text.MessageFormat; + +import ch.eitchnet.xmlpers.api.PersistenceContext; +import ch.eitchnet.xmlpers.objref.ObjectRef; + +public class AssertionUtil { + + public static void assertNotNull(Object object) { + if (object == null) + throw new RuntimeException("Object may not be null!"); //$NON-NLS-1$ + } + + public static void assertObjectRead(PersistenceContext context) { + if (context.getObject() == null) { + String msg = "Failed to read object with for {0}"; //$NON-NLS-1$ + ObjectRef objectRef = context.getObjectRef(); + msg = MessageFormat.format(msg, objectRef.getName()); + throw new RuntimeException(msg); + } + } + + public static void assertIsIdRef(ObjectRef objectRef) { + if (!objectRef.isLeaf()) { + String msg = "Expected IdRef not {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, objectRef.getName()); + throw new IllegalArgumentException(msg); + } + } + + public static void assertIsNotIdRef(ObjectRef objectRef) { + if (objectRef.isLeaf()) { + String msg = "IdRef not expected {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, objectRef.getName()); + throw new IllegalArgumentException(msg); + } + } + + public static void assertIsNotRootRef(ObjectRef objectRef) { + if (objectRef.isRoot()) { + String msg = "RootRef not expected {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/util/DomUtil.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/util/DomUtil.java new file mode 100644 index 000000000..fa2e9c9bf --- /dev/null +++ b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/util/DomUtil.java @@ -0,0 +1,43 @@ +/* + * 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.util; + +import java.text.MessageFormat; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import ch.eitchnet.xmlpers.api.XmlPersistenceException; + +/** + * @author Robert von Burg + * + */ +public class DomUtil { + + public static DocumentBuilder createDocumentBuilder() { + try { + DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder = dbfac.newDocumentBuilder(); + return docBuilder; + } catch (ParserConfigurationException e) { + String msg = "No Xml Parser could be loaded: {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, e.getMessage()); + throw new XmlPersistenceException(msg, e); + } + } +} diff --git a/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/util/FilenameUtility.java b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/util/FilenameUtility.java new file mode 100644 index 000000000..bd67391b5 --- /dev/null +++ b/ch.eitchnet.xmlpers/src/main/java/ch/eitchnet/xmlpers/util/FilenameUtility.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.util; + +import java.text.MessageFormat; + +import ch.eitchnet.xmlpers.api.XmlPersistenceException; +import ch.eitchnet.xmlpers.impl.PathBuilder; + +public class FilenameUtility { + + public static String getId(String filename) { + assertFilename(filename); + return filename.substring(0, filename.length() - PathBuilder.EXT_LENGTH); + } + + public static void assertFilename(String filename) { + if (filename.charAt(filename.length() - PathBuilder.EXT_LENGTH) != '.') { + String msg = "The filename does not have a . (dot) at index {0}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, (filename.length() - PathBuilder.EXT_LENGTH)); + throw new XmlPersistenceException(msg); + } + } + +} diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/AbstractPersistenceTest.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/AbstractPersistenceTest.java new file mode 100644 index 000000000..312a9f19e --- /dev/null +++ b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/AbstractPersistenceTest.java @@ -0,0 +1,75 @@ +/* + * 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.test; + +import java.io.File; +import java.util.Properties; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ch.eitchnet.utils.helper.FileHelper; +import ch.eitchnet.xmlpers.api.IoMode; +import ch.eitchnet.xmlpers.api.PersistenceConstants; +import ch.eitchnet.xmlpers.api.PersistenceManager; +import ch.eitchnet.xmlpers.api.PersistenceManagerLoader; +import ch.eitchnet.xmlpers.test.impl.BookContextFactory; +import ch.eitchnet.xmlpers.test.impl.MyModelContextFactory; +import ch.eitchnet.xmlpers.test.impl.TestConstants; +import ch.eitchnet.xmlpers.test.model.Book; +import ch.eitchnet.xmlpers.test.model.MyModel; + +public abstract class AbstractPersistenceTest { + + protected static final Logger logger = LoggerFactory.getLogger(AbstractPersistenceTest.class); + + protected PersistenceManager persistenceManager; + + @SuppressWarnings("nls") + protected static void cleanPath(String path) { + + File file = new File(path).getAbsoluteFile(); + File parent = file.getParentFile(); + if (!parent.getAbsolutePath().endsWith("/target/db")) + throw new RuntimeException("Bad parent! Must be /target/db/: " + parent); + if (!parent.exists() && !parent.mkdirs()) + throw new RuntimeException("Failed to create path " + parent); + + if (file.exists() && file.isDirectory()) + if (!FileHelper.deleteFiles(file.listFiles(), true)) + throw new RuntimeException("Could not clean up path " + file.getAbsolutePath()); + + if (!file.exists() && !file.mkdir()) + throw new RuntimeException("Failed to create path " + file); + + File domFile = new File(file, IoMode.DOM.name()); + if (!domFile.mkdir()) + throw new RuntimeException("Failed to create path " + domFile); + + File saxFile = new File(file, IoMode.SAX.name()); + if (!saxFile.mkdir()) + throw new RuntimeException("Failed to create path " + saxFile); + } + + protected void setup(Properties properties) { + properties.setProperty(PersistenceConstants.PROP_VERBOSE, "true"); //$NON-NLS-1$ + this.persistenceManager = PersistenceManagerLoader.load(properties); + this.persistenceManager.getCtxFactory().registerPersistenceContextFactory(MyModel.class, + TestConstants.TYPE_RES, new MyModelContextFactory()); + this.persistenceManager.getCtxFactory().registerPersistenceContextFactory(Book.class, TestConstants.TYPE_BOOK, + new BookContextFactory()); + } +} diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/FileDaoTest.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/FileDaoTest.java new file mode 100644 index 000000000..31f19d4d1 --- /dev/null +++ b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/FileDaoTest.java @@ -0,0 +1,122 @@ +/* + * 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.test; + +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.assertResource; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.assertResourceUpdated; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.createResource; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.updateResource; +import static org.junit.Assert.assertNull; + +import java.util.Properties; + +import org.junit.BeforeClass; +import org.junit.Test; + +import ch.eitchnet.xmlpers.api.FileDao; +import ch.eitchnet.xmlpers.api.IoMode; +import ch.eitchnet.xmlpers.api.PersistenceConstants; +import ch.eitchnet.xmlpers.api.PersistenceContext; +import ch.eitchnet.xmlpers.api.PersistenceContextFactory; +import ch.eitchnet.xmlpers.api.PersistenceContextFactoryDelegator; +import ch.eitchnet.xmlpers.api.PersistenceManager; +import ch.eitchnet.xmlpers.api.PersistenceTransaction; +import ch.eitchnet.xmlpers.impl.DefaultPersistenceRealm; +import ch.eitchnet.xmlpers.impl.DefaultPersistenceTransaction; +import ch.eitchnet.xmlpers.impl.PathBuilder; +import ch.eitchnet.xmlpers.objref.ObjectReferenceCache; +import ch.eitchnet.xmlpers.test.model.MyModel; + +/** + * @author Robert von Burg + * + */ +@SuppressWarnings("nls") +public class FileDaoTest extends AbstractPersistenceTest { + + private static final String TEST_PATH = "target/db/FileDaoTest/"; + private static final boolean VERBOSE = true; + private DefaultPersistenceRealm realm; + private PathBuilder pathBuilder; + + @BeforeClass + public static void beforeClass() { + cleanPath(TEST_PATH); + } + + private void setup(IoMode ioMode) { + Properties properties = new Properties(); + properties.setProperty(PersistenceConstants.PROP_BASEPATH, TEST_PATH + ioMode.name()); + setup(properties); + + ObjectReferenceCache objectRefCache = new ObjectReferenceCache(PersistenceManager.DEFAULT_REALM); + this.pathBuilder = new PathBuilder(PersistenceManager.DEFAULT_REALM, properties); + this.realm = new DefaultPersistenceRealm(PersistenceManager.DEFAULT_REALM, this.persistenceManager, + this.persistenceManager.getCtxFactory(), this.pathBuilder, objectRefCache); + } + + @Test + public void testCrudSax() { + setup(IoMode.SAX); + try (PersistenceTransaction tx = new DefaultPersistenceTransaction(this.realm, VERBOSE)) { + FileDao fileDao = new FileDao(tx, this.pathBuilder, VERBOSE); + tx.setIoMode(IoMode.SAX); + testCrud(this.realm.getCtxFactoryDelegator(), fileDao); + } + } + + @Test + public void testCrudDom() { + setup(IoMode.DOM); + try (PersistenceTransaction tx = new DefaultPersistenceTransaction(this.realm, VERBOSE)) { + FileDao fileDao = new FileDao(tx, this.pathBuilder, VERBOSE); + tx.setIoMode(IoMode.DOM); + testCrud(this.realm.getCtxFactoryDelegator(), fileDao); + } + } + + private void testCrud(PersistenceContextFactoryDelegator ctxFactoryDelegator, FileDao fileDao) { + + MyModel resource = createResource(); + assertResource(resource); + Class classType = resource.getClass(); + PersistenceContextFactory ctxFactory = ctxFactoryDelegator.getCtxFactory(classType); + ObjectReferenceCache objectRefCache = this.realm.getObjectRefCache(); + PersistenceContext context = ctxFactory.createCtx(objectRefCache, resource); + context.setObject(resource); + fileDao.performCreate(context); + + context.setObject(null); + fileDao.performRead(context); + assertResource(context.getObject()); + + updateResource(context.getObject()); + fileDao.performUpdate(context); + + context.setObject(null); + fileDao.performRead(context); + assertResourceUpdated(context.getObject()); + + fileDao.performDelete(context); + + context.setObject(null); + fileDao.performRead(context); + assertNull(context.getObject()); + + context.setObject(createResource()); + fileDao.performCreate(context); + } +} \ No newline at end of file diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java new file mode 100644 index 000000000..906d61a4c --- /dev/null +++ b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/LockingTest.java @@ -0,0 +1,233 @@ +/* + * 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.test; + +import static ch.eitchnet.xmlpers.test.impl.TestConstants.TYPE_RES; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.RES_TYPE; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.createResource; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.updateResource; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import ch.eitchnet.xmlpers.api.IoMode; +import ch.eitchnet.xmlpers.api.PersistenceConstants; +import ch.eitchnet.xmlpers.api.PersistenceTransaction; +import ch.eitchnet.xmlpers.objref.IdOfSubTypeRef; +import ch.eitchnet.xmlpers.objref.LockableObject; +import ch.eitchnet.xmlpers.test.model.MyModel; + +/** + * @author Robert von Burg + * + */ +public class LockingTest extends AbstractPersistenceTest { + + private static final String BASE_PATH = "target/db/LockingTest/"; //$NON-NLS-1$ + + private long waitForWorkersTime; + private boolean run; + + @BeforeClass + public static void beforeClass() { + cleanPath(BASE_PATH); + } + + @Before + public void before() { + Properties properties = new Properties(); + properties.setProperty(PersistenceConstants.PROP_BASEPATH, BASE_PATH + IoMode.DOM.name()); + properties.setProperty(PersistenceConstants.PROP_LOCK_TIME_MILLIS, Long.toString(500L)); + setup(properties); + + this.waitForWorkersTime = LockableObject.getLockTime() + getWaitForWorkersTime() + 300L; + } + + @Test + public void shouldLockObjects() throws InterruptedException { + + List workers = new ArrayList<>(5); + + String resoureId = "worker"; //$NON-NLS-1$ + for (int i = 0; i < 5; i++) { + String workerName = resoureId + "_" + i; //$NON-NLS-1$ + CreateResourceWorker worker = new CreateResourceWorker(workerName, workerName); + worker.start(); + workers.add(worker); + logger.info("Setup thread " + worker.getName()); //$NON-NLS-1$ + } + + int nrOfSuccess = runWorkers(workers); + + assertEquals("Only one thread should be able to perform the TX!", 5, nrOfSuccess); //$NON-NLS-1$ + } + + @Test + public void shouldFailIfResourceAlreadyExists() throws InterruptedException { + + List workers = new ArrayList<>(5); + + String resourceId = "createWorkerRes"; //$NON-NLS-1$ + for (int i = 0; i < 5; i++) { + String workerName = resourceId + "_" + i; //$NON-NLS-1$ + CreateResourceWorker worker = new CreateResourceWorker(workerName, resourceId); + worker.start(); + workers.add(worker); + logger.info("Setup thread " + worker.getName()); //$NON-NLS-1$ + } + + int nrOfSuccess = runWorkers(workers); + + assertEquals("Only one thread should be able to perform the TX!", 1, nrOfSuccess); //$NON-NLS-1$ + } + + @Test + public void shouldFailUpdateIfLockNotAcquirable() throws InterruptedException { + + // prepare workers + List workers = new ArrayList<>(5); + String resourceId = "updatWorkerRes"; //$NON-NLS-1$ + for (int i = 0; i < 5; i++) { + String workerName = resourceId + "_" + i; //$NON-NLS-1$ + UpdateResourceWorker worker = new UpdateResourceWorker(workerName, resourceId); + worker.start(); + workers.add(worker); + logger.info("Setup thread " + worker.getName()); //$NON-NLS-1$ + } + + // create resource which is to be updated + try (PersistenceTransaction tx = this.persistenceManager.openTx()) { + MyModel resource = createResource(resourceId); + tx.getObjectDao().add(resource); + } + + int nrOfSuccess = runWorkers(workers); + + assertEquals("Only one thread should be able to perform the TX!", 1, nrOfSuccess); //$NON-NLS-1$ + } + + private int runWorkers(List workers) throws InterruptedException { + + setRun(true); + + for (AbstractWorker worker : workers) { + worker.join(getWaitForWorkersTime() + 2000L); + } + + int nrOfSuccess = 0; + for (AbstractWorker worker : workers) { + if (worker.isSuccess()) + nrOfSuccess++; + } + + return nrOfSuccess; + } + + public long getWaitForWorkersTime() { + return this.waitForWorkersTime; + } + + public boolean isRun() { + return this.run; + } + + public void setRun(boolean run) { + this.run = run; + } + + public abstract class AbstractWorker extends Thread { + + protected boolean success; + protected String resourceId; + + public AbstractWorker(String name, String resourceId) { + super(name); + this.resourceId = resourceId; + } + + @Override + public void run() { + + logger.info("Waiting for ok to work..."); //$NON-NLS-1$ + while (!isRun()) { + try { + Thread.sleep(10L); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + logger.info("Starting work..."); //$NON-NLS-1$ + try (PersistenceTransaction tx = LockingTest.this.persistenceManager.openTx()) { + doWork(tx); + + try { + Thread.sleep(getWaitForWorkersTime()); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + this.success = true; + } + + logger.info("Work completed."); //$NON-NLS-1$ + } + + protected abstract void doWork(PersistenceTransaction tx); + + public boolean isSuccess() { + return this.success; + } + } + + public class CreateResourceWorker extends AbstractWorker { + + public CreateResourceWorker(String name, String resourceId) { + super(name, resourceId); + } + + @Override + protected void doWork(PersistenceTransaction tx) { + MyModel resource = createResource(this.resourceId); + tx.getObjectDao().add(resource); + } + } + + public class UpdateResourceWorker extends AbstractWorker { + + public UpdateResourceWorker(String name, String resourceId) { + super(name, resourceId); + } + + @Override + protected void doWork(PersistenceTransaction tx) { + + IdOfSubTypeRef objectRef = tx.getObjectRefCache().getIdOfSubTypeRef(TYPE_RES, RES_TYPE, this.resourceId); + MyModel resource = tx.getObjectDao().queryById(objectRef); + assertNotNull(resource); + updateResource(resource); + + tx.getObjectDao().update(resource); + } + } +} diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoBookTest.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoBookTest.java new file mode 100644 index 000000000..840d95e63 --- /dev/null +++ b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoBookTest.java @@ -0,0 +1,226 @@ +/* + * 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.test; + +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.BOOK_ID; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.assertBook; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.assertBookUpdated; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.createBook; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.updateBook; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import org.junit.Before; +import org.junit.Test; + +import ch.eitchnet.xmlpers.api.IoMode; +import ch.eitchnet.xmlpers.api.ObjectDao; +import ch.eitchnet.xmlpers.api.PersistenceConstants; +import ch.eitchnet.xmlpers.api.PersistenceTransaction; +import ch.eitchnet.xmlpers.objref.IdOfTypeRef; +import ch.eitchnet.xmlpers.objref.ObjectRef; +import ch.eitchnet.xmlpers.objref.TypeRef; +import ch.eitchnet.xmlpers.test.impl.TestConstants; +import ch.eitchnet.xmlpers.test.model.Book; + +/** + * @author Robert von Burg + * + */ +public class ObjectDaoBookTest extends AbstractPersistenceTest { + + private static final String BASEPATH = "target/db/ObjectDaoTest/"; //$NON-NLS-1$ + + @Before + public void before() { + cleanPath(BASEPATH); + } + + private void setup(IoMode ioMode) { + Properties properties = new Properties(); + properties.setProperty(PersistenceConstants.PROP_BASEPATH, BASEPATH + ioMode.name()); + setup(properties); + } + + @Test + public void testCrudSax() { + setup(IoMode.SAX); + testCrud(IoMode.SAX); + } + + @Test + public void testCrudDom() { + setup(IoMode.DOM); + testCrud(IoMode.DOM); + } + + private PersistenceTransaction freshTx(IoMode ioMode) { + PersistenceTransaction tx = this.persistenceManager.openTx(); + tx.setIoMode(ioMode); + return tx; + } + + private void testCrud(IoMode ioMode) { + + ObjectDao objectDao; + + // create new book + Book book = createBook(); + try (PersistenceTransaction tx = freshTx(ioMode);) { + objectDao = tx.getObjectDao(); + objectDao.add(book); + } + + // read book + try (PersistenceTransaction tx = freshTx(ioMode);) { + IdOfTypeRef bookRef = tx.getObjectRefCache() + .getIdOfTypeRef(TestConstants.TYPE_BOOK, Long.toString(BOOK_ID)); + objectDao = tx.getObjectDao(); + book = objectDao.queryById(bookRef); + assertBook(book); + + // modify book + updateBook(book); + objectDao.update(book); + } + + // read modified book + try (PersistenceTransaction tx = freshTx(ioMode);) { + IdOfTypeRef bookRef = tx.getObjectRefCache() + .getIdOfTypeRef(TestConstants.TYPE_BOOK, Long.toString(BOOK_ID)); + objectDao = tx.getObjectDao(); + book = objectDao.queryById(bookRef); + assertBookUpdated(book); + } + + // delete book + try (PersistenceTransaction tx = freshTx(ioMode);) { + objectDao = tx.getObjectDao(); + objectDao.remove(book); + } + + // fail to read + try (PersistenceTransaction tx = freshTx(ioMode);) { + IdOfTypeRef bookRef = tx.getObjectRefCache() + .getIdOfTypeRef(TestConstants.TYPE_BOOK, Long.toString(BOOK_ID)); + objectDao = tx.getObjectDao(); + book = objectDao.queryById(bookRef); + assertNull(book); + + // and create again + book = createBook(); + assertBook(book); + objectDao.add(book); + } + } + + @Test + public void testBulkSax() { + setup(IoMode.SAX); + testBulk(IoMode.SAX); + } + + @Test + public void testBulkDom() { + setup(IoMode.DOM); + testBulk(IoMode.DOM); + } + + private void testBulk(IoMode ioMode) { + + // create a list of books + List books = new ArrayList<>(10); + for (int i = 0; i < 10; i++) { + long id = i; + String title = "Bulk Test Book. " + i; //$NON-NLS-1$ + String author = "Nick Hornby"; //$NON-NLS-1$ + String press = "Penguin Books"; //$NON-NLS-1$ + double price = 21.30; + + Book book = createBook(id, title, author, press, price); + books.add(book); + } + + // save all + try (PersistenceTransaction tx = freshTx(ioMode);) { + ObjectDao objectDao = tx.getObjectDao(); + objectDao.addAll(books); + books.clear(); + } + + // query all + try (PersistenceTransaction tx = freshTx(ioMode);) { + TypeRef typeRef = tx.getObjectRefCache().getTypeRef(TestConstants.TYPE_BOOK); + ObjectDao objectDao = tx.getObjectDao(); + books = objectDao.queryAll(typeRef); + assertEquals("Expected to find 10 entries!", 10, books.size()); //$NON-NLS-1$ + + // delete them all + objectDao.removeAll(books); + } + + // now query them again + try (PersistenceTransaction tx = freshTx(ioMode);) { + TypeRef typeRef = tx.getObjectRefCache().getTypeRef(TestConstants.TYPE_BOOK); + ObjectDao objectDao = tx.getObjectDao(); + books = objectDao.queryAll(typeRef); + assertEquals("Expected to find 0 entries!", 0, books.size()); //$NON-NLS-1$ + } + } + + @Test + public void shouldPersistById() { + setup(IoMode.SAX); + + String classType = TestConstants.TYPE_BOOK; + long id = System.currentTimeMillis(); + String title = "About a boy"; //$NON-NLS-1$ + String author = "Nick Hornby"; //$NON-NLS-1$ + String press = "Penguin Books"; //$NON-NLS-1$ + double price = 21.30; + + // create a book + try (PersistenceTransaction tx = this.persistenceManager.openTx()) { + Book book = createBook(id, title, author, press, price); + tx.getObjectDao().add(book); + } + + // read by id + try (PersistenceTransaction tx = this.persistenceManager.openTx()) { + ObjectRef objectRef = tx.getObjectRefCache().getIdOfTypeRef(classType, Long.toString(id)); + Book book = tx.getObjectDao().queryById(objectRef); + assertNotNull("Expected to read book by ID", book); //$NON-NLS-1$ + } + + // delete by id + try (PersistenceTransaction tx = this.persistenceManager.openTx()) { + ObjectRef objectRef = tx.getObjectRefCache().getIdOfTypeRef(classType, Long.toString(id)); + tx.getObjectDao().removeById(objectRef); + } + + // fail to read by id + try (PersistenceTransaction tx = this.persistenceManager.openTx()) { + ObjectRef objectRef = tx.getObjectRefCache().getIdOfTypeRef(classType, Long.toString(id)); + Book book = tx.getObjectDao().queryById(objectRef); + assertNull("Expected that book was deleted by ID, thus can not be read anymore", book); //$NON-NLS-1$ + } + } +} diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoResourceTest.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoResourceTest.java new file mode 100644 index 000000000..72e40c3ae --- /dev/null +++ b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/ObjectDaoResourceTest.java @@ -0,0 +1,357 @@ +/* + * 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.test; + +import static ch.eitchnet.xmlpers.test.impl.TestConstants.TYPE_RES; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.RES_ID; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.RES_TYPE; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.assertResource; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.assertResourceUpdated; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.createResource; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.updateResource; +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import ch.eitchnet.xmlpers.api.IoMode; +import ch.eitchnet.xmlpers.api.ObjectDao; +import ch.eitchnet.xmlpers.api.PersistenceConstants; +import ch.eitchnet.xmlpers.api.PersistenceTransaction; +import ch.eitchnet.xmlpers.api.XmlPersistenceException; +import ch.eitchnet.xmlpers.objref.IdOfSubTypeRef; +import ch.eitchnet.xmlpers.objref.ObjectRef; +import ch.eitchnet.xmlpers.objref.SubTypeRef; +import ch.eitchnet.xmlpers.test.impl.TestConstants; +import ch.eitchnet.xmlpers.test.model.ModelBuilder; +import ch.eitchnet.xmlpers.test.model.MyModel; + +/** + * @author Robert von Burg + * + */ +public class ObjectDaoResourceTest extends AbstractPersistenceTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private static final String BASEPATH = "target/db/ObjectDaoTest/"; //$NON-NLS-1$ + + @BeforeClass + public static void beforeClass() { + cleanPath(BASEPATH); + } + + private void setup(IoMode ioMode) { + Properties properties = new Properties(); + properties.setProperty(PersistenceConstants.PROP_BASEPATH, BASEPATH + ioMode.name()); + setup(properties); + } + + @Test + public void testCrudSax() { + setup(IoMode.SAX); + testCrud(IoMode.SAX); + } + + @Test + public void testCrudDom() { + setup(IoMode.DOM); + testCrud(IoMode.DOM); + } + + private PersistenceTransaction freshTx(IoMode ioMode) { + PersistenceTransaction tx = this.persistenceManager.openTx(); + tx.setIoMode(ioMode); + return tx; + } + + private void testCrud(IoMode ioMode) { + + ObjectDao objectDao; + + // create new resource + MyModel resource = createResource(); + try (PersistenceTransaction tx = freshTx(ioMode);) { + objectDao = tx.getObjectDao(); + objectDao.add(resource); + } + + // read resource + try (PersistenceTransaction tx = freshTx(ioMode);) { + IdOfSubTypeRef resRef = tx.getObjectRefCache().getIdOfSubTypeRef(TestConstants.TYPE_RES, RES_TYPE, RES_ID); + objectDao = tx.getObjectDao(); + resource = objectDao.queryById(resRef); + assertResource(resource); + + // modify resource + updateResource(resource); + objectDao.update(resource); + } + + // read modified resource + try (PersistenceTransaction tx = freshTx(ioMode);) { + IdOfSubTypeRef resRef = tx.getObjectRefCache().getIdOfSubTypeRef(TestConstants.TYPE_RES, RES_TYPE, RES_ID); + objectDao = tx.getObjectDao(); + resource = objectDao.queryById(resRef); + assertResourceUpdated(resource); + } + + // delete resource + try (PersistenceTransaction tx = freshTx(ioMode);) { + objectDao = tx.getObjectDao(); + objectDao.remove(resource); + } + + // fail to read + try (PersistenceTransaction tx = freshTx(ioMode);) { + IdOfSubTypeRef resRef = tx.getObjectRefCache().getIdOfSubTypeRef(TestConstants.TYPE_RES, RES_TYPE, RES_ID); + objectDao = tx.getObjectDao(); + resource = objectDao.queryById(resRef); + assertNull(resource); + + // and create again + resource = createResource(); + assertResource(resource); + objectDao.add(resource); + } + } + + @Test + public void testBulkSax() { + setup(IoMode.SAX); + testBulk(IoMode.SAX); + } + + @Test + public void testBulkDom() { + setup(IoMode.DOM); + testBulk(IoMode.DOM); + } + + private void testBulk(IoMode ioMode) { + + // context + String type = "testBulk" + ioMode.name(); //$NON-NLS-1$ + + // create a list of resources + List resources = new ArrayList<>(10); + for (int i = 0; i < 10; i++) { + String id = RES_ID + "_" + i; //$NON-NLS-1$ + String name = "Bulk Test Object. " + i; //$NON-NLS-1$ + + MyModel resource = createResource(id, name, type); + resources.add(resource); + } + + // save all + try (PersistenceTransaction tx = freshTx(ioMode);) { + ObjectDao objectDao = tx.getObjectDao(); + objectDao.addAll(resources); + resources.clear(); + } + + // query all + try (PersistenceTransaction tx = freshTx(ioMode);) { + SubTypeRef subTypeRef = tx.getObjectRefCache().getSubTypeRef(TestConstants.TYPE_RES, type); + ObjectDao objectDao = tx.getObjectDao(); + resources = objectDao.queryAll(subTypeRef); + assertEquals("Expected to find 10 entries!", 10, resources.size()); //$NON-NLS-1$ + + // delete them all + objectDao.removeAll(resources); + } + + // now query them again + try (PersistenceTransaction tx = freshTx(ioMode);) { + SubTypeRef subTypeRef = tx.getObjectRefCache().getSubTypeRef(TestConstants.TYPE_RES, type); + ObjectDao objectDao = tx.getObjectDao(); + resources = objectDao.queryAll(subTypeRef); + assertEquals("Expected to find 0 entries!", 0, resources.size()); //$NON-NLS-1$ + } + } + + @Test + public void shouldPersistById() { + setup(IoMode.SAX); + + String classType = TestConstants.TYPE_RES; + String subType = ModelBuilder.RES_TYPE; + String id = "shouldPersistById"; //$NON-NLS-1$ + String name = "shouldPersistById "; //$NON-NLS-1$ + + // create a resource + try (PersistenceTransaction tx = this.persistenceManager.openTx()) { + MyModel resource = createResource(id, name, subType); + tx.getObjectDao().add(resource); + } + + // read by id + try (PersistenceTransaction tx = this.persistenceManager.openTx()) { + ObjectRef objectRef = tx.getObjectRefCache().getIdOfSubTypeRef(classType, subType, id); + MyModel resource = tx.getObjectDao().queryById(objectRef); + assertNotNull("Expected to read resource by ID", resource); //$NON-NLS-1$ + } + + // delete by id + try (PersistenceTransaction tx = this.persistenceManager.openTx()) { + ObjectRef objectRef = tx.getObjectRefCache().getIdOfSubTypeRef(classType, subType, id); + tx.getObjectDao().removeById(objectRef); + } + + // fail to read by id + try (PersistenceTransaction tx = this.persistenceManager.openTx()) { + ObjectRef objectRef = tx.getObjectRefCache().getIdOfSubTypeRef(classType, subType, id); + MyModel resource = tx.getObjectDao().queryById(objectRef); + assertNull("Expected that resource was deleted by ID, thus can not be read anymore", resource); //$NON-NLS-1$ + } + } + + @Test + public void shouldFailModifyNotExisting() { + setup(IoMode.SAX); + + this.thrown.expect(XmlPersistenceException.class); + this.thrown.expectMessage(containsString("Persistence unit does not exist for")); //$NON-NLS-1$ + + // update + try (PersistenceTransaction tx = this.persistenceManager.openTx()) { + + MyModel resource = createResource(); + tx.getObjectDao().update(resource); + } + } + + @Test + public void shouldFailDeleteNotExisting() { + setup(IoMode.SAX); + + this.thrown.expect(XmlPersistenceException.class); + this.thrown.expectMessage(containsString("Persistence unit does not exist for")); //$NON-NLS-1$ + + // delete + try (PersistenceTransaction tx = this.persistenceManager.openTx()) { + + MyModel resource = createResource(); + tx.getObjectDao().remove(resource); + } + } + + @Test + public void shouldAllowAllOperationsInSameTx() { + setup(IoMode.SAX); + + String subType = ModelBuilder.RES_TYPE; + String name = "shouldPersistById "; //$NON-NLS-1$ + + // create + try (PersistenceTransaction tx = this.persistenceManager.openTx()) { + + String id = "shouldAllowAllOperationsInSameTx_create"; //$NON-NLS-1$ + MyModel resource = createResource(id, name, subType); + + tx.getObjectDao().add(resource); + } + + // create / modify + try (PersistenceTransaction tx = this.persistenceManager.openTx()) { + + String id = "shouldAllowAllOperationsInSameTx_create_modify"; //$NON-NLS-1$ + MyModel resource = createResource(id, name, subType); + + tx.getObjectDao().add(resource); + tx.getObjectDao().update(resource); + } + + // create / delete + try (PersistenceTransaction tx = this.persistenceManager.openTx()) { + + String id = "shouldAllowAllOperationsInSameTx_create_delete"; //$NON-NLS-1$ + MyModel resource = createResource(id, name, subType); + + tx.getObjectDao().add(resource); + tx.getObjectDao().remove(resource); + } + + // create / modify / delete + try (PersistenceTransaction tx = this.persistenceManager.openTx()) { + + String id = "shouldAllowAllOperationsInSameTx_create_modify_delete"; //$NON-NLS-1$ + MyModel resource = createResource(id, name, subType); + + tx.getObjectDao().add(resource); + tx.getObjectDao().update(resource); + tx.getObjectDao().remove(resource); + } + + String id = "shouldAllowAllOperationsInSameTx_read_modify"; //$NON-NLS-1$ + + // prepare for read/modify + try (PersistenceTransaction tx = this.persistenceManager.openTx()) { + MyModel resource = createResource(id, name, subType); + tx.getObjectDao().add(resource); + } + + // read / modify + try (PersistenceTransaction tx = this.persistenceManager.openTx()) { + + ObjectRef objectRef = tx.getObjectRefCache().getIdOfSubTypeRef(TYPE_RES, subType, id); + Object resource = tx.getObjectDao().queryById(objectRef); + assertNotNull(resource); + tx.getObjectDao().update(resource); + } + + // read / delete + try (PersistenceTransaction tx = this.persistenceManager.openTx()) { + + ObjectRef objectRef = tx.getObjectRefCache().getIdOfSubTypeRef(TYPE_RES, subType, id); + Object resource = tx.getObjectDao().queryById(objectRef); + assertNotNull(resource); + tx.getObjectDao().remove(resource); + } + + // make sure deleted, then recreate + try (PersistenceTransaction tx = this.persistenceManager.openTx()) { + + ObjectRef objectRef = tx.getObjectRefCache().getIdOfSubTypeRef(TYPE_RES, subType, id); + Object resource = tx.getObjectDao().queryById(objectRef); + assertNull(resource); + + // recreate + resource = createResource(id, name, subType); + tx.getObjectDao().add(resource); + } + + // read / modify / delete + try (PersistenceTransaction tx = this.persistenceManager.openTx()) { + + ObjectRef objectRef = tx.getObjectRefCache().getIdOfSubTypeRef(TYPE_RES, subType, id); + Object resource = tx.getObjectDao().queryById(objectRef); + assertNotNull(resource); + tx.getObjectDao().update(resource); + tx.getObjectDao().remove(resource); + } + } +} diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/RealmTest.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/RealmTest.java new file mode 100644 index 000000000..d3f91e337 --- /dev/null +++ b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/RealmTest.java @@ -0,0 +1,125 @@ +/* + * 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.test; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import java.util.Properties; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import ch.eitchnet.xmlpers.api.IoMode; +import ch.eitchnet.xmlpers.api.PersistenceConstants; +import ch.eitchnet.xmlpers.api.PersistenceTransaction; +import ch.eitchnet.xmlpers.objref.ObjectRef; +import ch.eitchnet.xmlpers.test.impl.TestConstants; +import ch.eitchnet.xmlpers.test.model.ModelBuilder; +import ch.eitchnet.xmlpers.test.model.MyModel; + +@SuppressWarnings("nls") +public class RealmTest extends AbstractPersistenceTest { + + private static final String REALM_2 = "Realm2"; + private static final String REALM_1 = "Realm1"; + protected static final String BASE_PATH = "target/db/RealmTest/"; + + @BeforeClass + public static void beforeClass() { + cleanPath(BASE_PATH); + } + + @Before + public void before() { + Properties properties = new Properties(); + properties.setProperty(PersistenceConstants.PROP_BASEPATH, BASE_PATH + IoMode.DOM.name()); + setup(properties); + } + + @Test + public void shouldNotFindObjInBothRealms() { + + // object details + String objType = TestConstants.TYPE_RES; + String type = ModelBuilder.RES_TYPE; + String name = ModelBuilder.RES_NAME; + String id = "shouldNotFindObjInBothRealms"; + + // create in first realm + try (PersistenceTransaction txRealm1 = this.persistenceManager.openTx(REALM_1);) { + MyModel resource1 = ModelBuilder.createResource(id, name, type); + txRealm1.getObjectDao().add(resource1); + } + + // find in first realm + try (PersistenceTransaction txRealm1 = this.persistenceManager.openTx(REALM_1);) { + ObjectRef objectRef = txRealm1.getObjectRefCache().getIdOfSubTypeRef(objType, type, id); + MyModel resource = txRealm1.getObjectDao().queryById(objectRef); + assertNotNull("Resource was not found in first realm!", resource); + } + + // fail to find in second realm + try (PersistenceTransaction txRealm2 = this.persistenceManager.openTx(REALM_2);) { + ObjectRef objectRef = txRealm2.getObjectRefCache().getIdOfSubTypeRef(objType, type, id); + MyModel resource = txRealm2.getObjectDao().queryById(objectRef); + assertNull("Resource was not created in second realm, thus not expected to be found there!", resource); + } + } + + @Test + public void shouldNotDeleteObjInWrongRealm() { + + // object details + String objType = TestConstants.TYPE_RES; + String subType = ModelBuilder.RES_TYPE; + String name = ModelBuilder.RES_NAME; + String id = "shouldNotDeleteObjInWrongRealm"; + + // create in first realm + try (PersistenceTransaction txRealm1 = this.persistenceManager.openTx(REALM_1);) { + MyModel resource1 = ModelBuilder.createResource(id, name, subType); + txRealm1.getObjectDao().add(resource1); + } + + // create in second realm + try (PersistenceTransaction txRealm2 = this.persistenceManager.openTx(REALM_2);) { + MyModel resource1 = ModelBuilder.createResource(id, name, subType); + txRealm2.getObjectDao().add(resource1); + } + + // delete in second realm + try (PersistenceTransaction txRealm2 = this.persistenceManager.openTx(REALM_2);) { + ObjectRef objectRef = txRealm2.getObjectRefCache().getIdOfSubTypeRef(objType, subType, id); + txRealm2.getObjectDao().removeById(objectRef); + } + + // fail to find in second realm + try (PersistenceTransaction txRealm2 = this.persistenceManager.openTx(REALM_2);) { + ObjectRef objectRef = txRealm2.getObjectRefCache().getIdOfSubTypeRef(objType, subType, id); + MyModel resource = txRealm2.getObjectDao().queryById(objectRef); + assertNull("Resource was not deleted from second realm, thus not expected to be found there!", resource); + } + + // find in first realm + try (PersistenceTransaction txRealm1 = this.persistenceManager.openTx(REALM_1);) { + ObjectRef objectRef = txRealm1.getObjectRefCache().getIdOfSubTypeRef(objType, subType, id); + MyModel resource = txRealm1.getObjectDao().queryById(objectRef); + assertNotNull("Resource was not found in first realm!", resource); + } + } +} diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/TransactionResultTest.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/TransactionResultTest.java new file mode 100644 index 000000000..8aa5e5a53 --- /dev/null +++ b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/TransactionResultTest.java @@ -0,0 +1,135 @@ +/* + * 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.test; + +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.RES_ID; +import static ch.eitchnet.xmlpers.test.model.ModelBuilder.createResource; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import ch.eitchnet.xmlpers.api.IoMode; +import ch.eitchnet.xmlpers.api.ModificationResult; +import ch.eitchnet.xmlpers.api.ObjectDao; +import ch.eitchnet.xmlpers.api.PersistenceConstants; +import ch.eitchnet.xmlpers.api.PersistenceTransaction; +import ch.eitchnet.xmlpers.api.TransactionResult; +import ch.eitchnet.xmlpers.test.model.Book; +import ch.eitchnet.xmlpers.test.model.MyModel; + +/** + * @author Robert von Burg + * + */ +public class TransactionResultTest extends AbstractPersistenceTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private static final String BASEPATH = "target/db/TxResultTest/"; //$NON-NLS-1$ + + @Before + public void setup() { + cleanPath(BASEPATH); + Properties properties = new Properties(); + properties.setProperty(PersistenceConstants.PROP_BASEPATH, BASEPATH); + setup(properties); + } + + private PersistenceTransaction freshTx() { + PersistenceTransaction tx = this.persistenceManager.openTx(); + tx.setIoMode(IoMode.SAX); + return tx; + } + + @Test + public void testWithTxResult() { + + TransactionResult txResult = new TransactionResult(); + performChanges(txResult); + String logMessage = txResult.getLogMessage(); + logger.info(logMessage); + assertThat(logMessage, containsString("TX for realm defaultRealm was completed after")); //$NON-NLS-1$ + assertThat(logMessage, containsString("30 objects in 2 types were modified")); //$NON-NLS-1$ + assertThat(txResult.getKeys(), containsInAnyOrder("Resource", "Book")); //$NON-NLS-1$ //$NON-NLS-2$ + + ModificationResult resourceModificationResult = txResult.getModificationResult("Resource"); //$NON-NLS-1$ + assertEquals(20, resourceModificationResult.getCreated().size()); + assertEquals(0, resourceModificationResult.getUpdated().size()); + assertEquals(0, resourceModificationResult.getDeleted().size()); + + ModificationResult bookModificationResult = txResult.getModificationResult("Book"); //$NON-NLS-1$ + assertEquals(10, bookModificationResult.getCreated().size()); + assertEquals(0, bookModificationResult.getUpdated().size()); + assertEquals(0, bookModificationResult.getDeleted().size()); + } + + @Test + public void testWithoutTxResult() { + performChanges(null); + } + + private void performChanges(TransactionResult txResult) { + + // create a list of resources + List resources = new ArrayList<>(10); + int i = 0; + for (; i < 10; i++) { + String id = RES_ID + "_" + i; //$NON-NLS-1$ + String name = "Tx Result Test 1 Object. " + i; //$NON-NLS-1$ + String type = "testTxResult1"; //$NON-NLS-1$ + + MyModel resource = createResource(id, name, type); + resources.add(resource); + } + for (; i < 20; i++) { + String id = RES_ID + "_" + i; //$NON-NLS-1$ + String name = "Tx Result Test 2 Object. " + i; //$NON-NLS-1$ + String type = "testTxResult2"; //$NON-NLS-1$ + + MyModel resource = createResource(id, name, type); + resources.add(resource); + } + + // create a list of books + List books = new ArrayList<>(10); + i = 0; + for (; i < 10; i++) { + String title = "Tx Result Test Book " + i; //$NON-NLS-1$ + Book book = new Book((long) i, title, "Douglas Adams", "Apress", Math.random() * i); //$NON-NLS-1$ //$NON-NLS-2$ + books.add(book); + } + + // save all + try (PersistenceTransaction tx = freshTx();) { + tx.setTransactionResult(txResult); + ObjectDao objectDao = tx.getObjectDao(); + objectDao.addAll(resources); + objectDao.addAll(books); + resources.clear(); + } + } +} diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java new file mode 100644 index 000000000..32af884e2 --- /dev/null +++ b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/XmlTestMain.java @@ -0,0 +1,298 @@ +/* + * 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.test; + +import java.io.File; +import java.io.FileWriter; +import java.util.ArrayList; +import java.util.List; + +import javanet.staxutils.IndentingXMLStreamWriter; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamWriter; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +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.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import ch.eitchnet.utils.exceptions.XmlException; +import ch.eitchnet.utils.helper.XmlHelper; +import ch.eitchnet.xmlpers.test.model.ModelBuilder; +import ch.eitchnet.xmlpers.test.model.MyModel; +import ch.eitchnet.xmlpers.test.model.MyParameter; + +/** + * @author Robert von Burg + * + */ +@SuppressWarnings("nls") +public class XmlTestMain { + + private static final Logger logger = LoggerFactory.getLogger(XmlTestMain.class); + + private static MyModel res; + + public static void main(String[] args) throws Exception { + + res = ModelBuilder.createResource(); + + logger.info("Writing Res:\n" + res); + + writeSax(res); + writeDom(res); + + List resoures; + resoures = readDom(); + logger.info("Parsed Resources:\n" + resoures); + resoures = readSax(); + logger.info("Parsed Resources:\n" + resoures); + } + + /** + * @return + * + */ + private static List readDom() throws Exception { + + File file = new File("target/res_dom.xml"); + DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder = dbfac.newDocumentBuilder(); + Document document = docBuilder.parse(file); + Element rootElement = document.getDocumentElement(); + + List resources = new ArrayList<>(); + + NodeList resElements = rootElement.getElementsByTagName("Resource"); + for (int i = 0; i < resElements.getLength(); i++) { + Element resElement = (Element) resElements.item(i); + String id = resElement.getAttribute("id"); + String name = resElement.getAttribute("name"); + String type = resElement.getAttribute("type"); + + MyModel res = new MyModel(); + res.setId(id); + res.setName(name); + res.setType(type); + + NodeList paramElements = resElement.getElementsByTagName("Parameter"); + parseParameters(res, paramElements); + + resources.add(res); + } + + logger.info("DOM parsed file " + file.getAbsolutePath()); + + return resources; + } + + private static void parseParameters(MyModel res, NodeList paramElements) { + for (int i = 0; i < paramElements.getLength(); i++) { + Element paramElement = (Element) paramElements.item(i); + String id = paramElement.getAttribute("id"); + String name = paramElement.getAttribute("name"); + String type = paramElement.getAttribute("type"); + String value = paramElement.getAttribute("value"); + + MyParameter param = new MyParameter(); + param.setId(id); + param.setName(name); + param.setType(type); + param.setValue(value); + + res.addParameter(param); + } + } + + /** + * @return + * + */ + private static List readSax() throws Exception { + + final List resources = new ArrayList<>(); + final MyModel[] currentRes = new MyModel[1]; + + DefaultHandler xmlHandler = new DefaultHandler() { + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) + throws SAXException { + + switch (qName) { + case "Resource": + MyModel res = new MyModel(); + res.setId(attributes.getValue("id")); + res.setName(attributes.getValue("name")); + res.setType(attributes.getValue("type")); + currentRes[0] = res; + resources.add(res); + break; + case "Parameter": + MyParameter param = new MyParameter(); + param.setId(attributes.getValue("id")); + param.setName(attributes.getValue("name")); + param.setType(attributes.getValue("type")); + param.setValue(attributes.getValue("value")); + currentRes[0].addParameter(param); + break; + case "model": + break; + default: + throw new IllegalArgumentException("The element '" + qName + "' is unhandled!"); + } + } + }; + + SAXParserFactory spf = SAXParserFactory.newInstance(); + SAXParser sp = spf.newSAXParser(); + File file = new File("target/res_sax.xml"); + sp.parse(file, xmlHandler); + + logger.info("SAX parsed file " + file.getAbsolutePath()); + + return resources; + } + + private static void writeDom(MyModel res) throws Exception { + + DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder = dbfac.newDocumentBuilder(); + Document doc = docBuilder.newDocument(); + + Element resElement = doc.createElement("Resource"); + resElement.setAttribute("id", res.getId()); + resElement.setAttribute("name", res.getName()); + resElement.setAttribute("type", res.getType()); + + for (String paramId : res.getParameterKeySet()) { + MyParameter param = res.getParameterBy(paramId); + Element paramElement = doc.createElement("Parameter"); + paramElement.setAttribute("id", param.getId()); + paramElement.setAttribute("name", param.getName()); + paramElement.setAttribute("type", param.getType()); + paramElement.setAttribute("value", param.getValue()); + resElement.appendChild(paramElement); + } + + Element rootElement = doc.createElement("model"); + rootElement.appendChild(resElement); + + doc.appendChild(rootElement); + + String lineSep = System.getProperty(XmlHelper.PROP_LINE_SEPARATOR); + try { + + String encoding = doc.getInputEncoding(); + if (encoding == null || encoding.isEmpty()) { + logger.info("No encoding passed. Using default encoding " + XmlHelper.DEFAULT_ENCODING); + encoding = XmlHelper.DEFAULT_ENCODING; + } + + if (!lineSep.equals("\n")) { + logger.info("Overriding line separator to \\n"); + System.setProperty(XmlHelper.PROP_LINE_SEPARATOR, "\n"); + } + + // Set up a transformer + TransformerFactory transfac = TransformerFactory.newInstance(); + Transformer transformer = transfac.newTransformer(); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty(OutputKeys.METHOD, "xml"); + transformer.setOutputProperty(OutputKeys.ENCODING, encoding); + transformer.setOutputProperty("{http://xml.apache.org/xalan}indent-amount", "2"); + // transformer.setOutputProperty("{http://xml.apache.org/xalan}line-separator", "\t"); + + // Transform to file + File file = new File("target/res_dom.xml"); + StreamResult result = new StreamResult(file); + Source xmlSource = new DOMSource(doc); + transformer.transform(xmlSource, result); + + logger.info("Wrote DOM to " + file.getAbsolutePath()); + + } catch (Exception e) { + + throw new XmlException("Exception while exporting to file: " + e, e); + + } finally { + + System.setProperty(XmlHelper.PROP_LINE_SEPARATOR, lineSep); + } + } + + private static void writeSax(MyModel res) throws Exception { + XMLOutputFactory factory = XMLOutputFactory.newInstance(); + + File file = new File("target/res_sax.xml"); + try (FileWriter fileWriter = new FileWriter(file)) { + XMLStreamWriter writer = factory.createXMLStreamWriter(fileWriter); + + writer = new IndentingXMLStreamWriter(writer); + + writer.writeStartDocument("utf-8", "1.0"); + writer.writeStartElement("model"); + + writer.writeStartElement("Resource"); + writer.writeAttribute("id", res.getId()); + writer.writeAttribute("name", res.getName()); + writer.writeAttribute("type", res.getType()); + + for (String paramId : res.getParameterKeySet()) { + MyParameter param = res.getParameterBy(paramId); + writer.writeEmptyElement("Parameter"); + writer.writeAttribute("id", param.getId()); + writer.writeAttribute("name", param.getName()); + writer.writeAttribute("type", param.getType()); + writer.writeAttribute("value", param.getValue()); + } + + //writer.writeEmptyElement("data"); + //writer.writeAttribute("name", "value"); + ////writer.writeEndElement(); + //writer.writeEmptyElement("stuff"); + //writer.writeAttribute("attr", "attrVal"); + + writer.writeEndElement(); + writer.writeEndDocument(); + + writer.flush(); + writer.close(); + logger.info("Wrote SAX to " + file.getAbsolutePath()); + + //Transformer transformer = TransformerFactory.newInstance().newTransformer(); + //Result outputTarget = new StaxR; + //Source xmlSource; + //transformer.transform(xmlSource, outputTarget); + } + } +} diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookContextFactory.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookContextFactory.java new file mode 100644 index 000000000..50563cb82 --- /dev/null +++ b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookContextFactory.java @@ -0,0 +1,41 @@ +/* + * 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.test.impl; + +import ch.eitchnet.xmlpers.api.PersistenceContext; +import ch.eitchnet.xmlpers.api.PersistenceContextFactory; +import ch.eitchnet.xmlpers.objref.IdOfTypeRef; +import ch.eitchnet.xmlpers.objref.ObjectRef; +import ch.eitchnet.xmlpers.objref.ObjectReferenceCache; +import ch.eitchnet.xmlpers.test.model.Book; + +public class BookContextFactory implements PersistenceContextFactory { + + @Override + public PersistenceContext createCtx(ObjectRef objectRef) { + PersistenceContext ctx = new PersistenceContext<>(objectRef); + ctx.setParserFactory(new BookParserFactory()); + return ctx; + } + + @Override + public PersistenceContext createCtx(ObjectReferenceCache objectRefCache, Book t) { + IdOfTypeRef objectRef = objectRefCache.getIdOfTypeRef(TestConstants.TYPE_BOOK, t.getId().toString()); + PersistenceContext ctx = createCtx(objectRef); + ctx.setObject(t); + return ctx; + } +} diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomParser.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomParser.java new file mode 100644 index 000000000..a07e19be8 --- /dev/null +++ b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookDomParser.java @@ -0,0 +1,81 @@ +/* + * 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.test.impl; + +import javax.xml.parsers.DocumentBuilder; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import ch.eitchnet.xmlpers.api.DomParser; +import ch.eitchnet.xmlpers.test.model.Book; +import ch.eitchnet.xmlpers.util.DomUtil; + +/** + * @author Robert von Burg + * + */ +public class BookDomParser implements DomParser { + + private Book book; + + @Override + public Book getObject() { + return this.book; + } + + @Override + public void setObject(Book object) { + this.book = object; + } + + @SuppressWarnings("nls") + @Override + public Document toDom() { + + DocumentBuilder documentBuilder = DomUtil.createDocumentBuilder(); + Document document = documentBuilder.getDOMImplementation().createDocument(null, null, null); + + Element rootElement = document.createElement("Book"); + document.appendChild(rootElement); + rootElement.setAttribute("id", Long.toString(this.book.getId())); + rootElement.setAttribute("title", this.book.getTitle()); + rootElement.setAttribute("author", this.book.getAuthor()); + rootElement.setAttribute("press", this.book.getPress()); + rootElement.setAttribute("price", Double.toString(this.book.getPrice())); + + return document; + + } + + @SuppressWarnings("nls") + @Override + public void fromDom(Document document) { + + Element rootElement = document.getDocumentElement(); + + String idS = rootElement.getAttribute("id"); + long id = Long.parseLong(idS); + String title = rootElement.getAttribute("title"); + String author = rootElement.getAttribute("author"); + String press = rootElement.getAttribute("press"); + String priceS = rootElement.getAttribute("price"); + double price = Double.parseDouble(priceS); + + Book book = new Book(id, title, author, press, price); + this.book = book; + } +} diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookParserFactory.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookParserFactory.java new file mode 100644 index 000000000..9de53c54e --- /dev/null +++ b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookParserFactory.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.test.impl; + +import ch.eitchnet.xmlpers.api.DomParser; +import ch.eitchnet.xmlpers.api.ParserFactory; +import ch.eitchnet.xmlpers.api.SaxParser; +import ch.eitchnet.xmlpers.test.model.Book; + +/** + * @author Robert von Burg + * + */ +public class BookParserFactory implements ParserFactory { + + @Override + public DomParser getDomParser() { + return new BookDomParser(); + } + + @Override + public SaxParser getSaxParser() { + return new BookSaxParser(); + } +} diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxParser.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxParser.java new file mode 100644 index 000000000..154df1afa --- /dev/null +++ b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/BookSaxParser.java @@ -0,0 +1,85 @@ +/* + * 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.test.impl; + +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import ch.eitchnet.xmlpers.api.SaxParser; +import ch.eitchnet.xmlpers.test.model.Book; + +/** + * @author Robert von Burg + * + */ +public class BookSaxParser extends DefaultHandler implements SaxParser { + + private Book book; + + @Override + public Book getObject() { + return this.book; + } + + @Override + public void setObject(Book object) { + this.book = object; + + } + + @Override + public DefaultHandler getDefaultHandler() { + return this; + } + + @SuppressWarnings("nls") + @Override + public void write(XMLStreamWriter writer) throws XMLStreamException { + + writer.writeEmptyElement("Book"); + writer.writeAttribute("id", Long.toString(this.book.getId())); + writer.writeAttribute("title", this.book.getTitle()); + writer.writeAttribute("author", this.book.getAuthor()); + writer.writeAttribute("press", this.book.getPress()); + writer.writeAttribute("price", Double.toString(this.book.getPrice())); + } + + @SuppressWarnings("nls") + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + + switch (qName) { + case "Book": + String idS = attributes.getValue("id"); + long id = Long.parseLong(idS); + Book book = new Book(id); + book.setTitle(attributes.getValue("title")); + book.setAuthor(attributes.getValue("author")); + book.setPress(attributes.getValue("press")); + String priceS = attributes.getValue("price"); + double price = Double.parseDouble(priceS); + book.setPrice(price); + this.book = book; + break; + default: + throw new IllegalArgumentException("The element '" + qName + "' is unhandled!"); + } + } +} diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelContextFactory.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelContextFactory.java new file mode 100644 index 000000000..718813d46 --- /dev/null +++ b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelContextFactory.java @@ -0,0 +1,42 @@ +/* + * 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.test.impl; + +import ch.eitchnet.xmlpers.api.PersistenceContext; +import ch.eitchnet.xmlpers.api.PersistenceContextFactory; +import ch.eitchnet.xmlpers.objref.IdOfSubTypeRef; +import ch.eitchnet.xmlpers.objref.ObjectRef; +import ch.eitchnet.xmlpers.objref.ObjectReferenceCache; +import ch.eitchnet.xmlpers.test.model.MyModel; + +public class MyModelContextFactory implements PersistenceContextFactory { + + @Override + public PersistenceContext createCtx(ObjectReferenceCache objectRefCache, MyModel t) { + IdOfSubTypeRef objectRef = objectRefCache.getIdOfSubTypeRef(TestConstants.TYPE_RES, t.getType(), t.getId()); + PersistenceContext ctx = createCtx(objectRef); + ctx.setObject(t); + return ctx; + } + + @Override + public PersistenceContext createCtx(ObjectRef objectRef) { + PersistenceContext ctx = new PersistenceContext<>(objectRef); + ctx.setParserFactory(new MyModelParserFactory()); + return ctx; + } + +} \ No newline at end of file diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelDomParser.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelDomParser.java new file mode 100644 index 000000000..30859879e --- /dev/null +++ b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelDomParser.java @@ -0,0 +1,102 @@ +/* + * 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.test.impl; + +import javax.xml.parsers.DocumentBuilder; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import ch.eitchnet.xmlpers.api.DomParser; +import ch.eitchnet.xmlpers.test.model.MyModel; +import ch.eitchnet.xmlpers.test.model.MyParameter; +import ch.eitchnet.xmlpers.util.DomUtil; + +public class MyModelDomParser implements DomParser { + + private MyModel resource; + + @Override + public MyModel getObject() { + return this.resource; + } + + @Override + public void setObject(MyModel resource) { + this.resource = resource; + } + + @SuppressWarnings("nls") + @Override + public Document toDom() { + + DocumentBuilder documentBuilder = DomUtil.createDocumentBuilder(); + Document document = documentBuilder.getDOMImplementation().createDocument(null, null, null); + + Element element = document.createElement("Resource"); + document.appendChild(element); + + element.setAttribute("id", this.resource.getId()); + element.setAttribute("name", this.resource.getName()); + element.setAttribute("type", this.resource.getType()); + + for (String paramId : this.resource.getParameterKeySet()) { + MyParameter param = this.resource.getParameterBy(paramId); + Element paramElement = document.createElement("Parameter"); + element.appendChild(paramElement); + + paramElement.setAttribute("id", param.getId()); + paramElement.setAttribute("name", param.getName()); + paramElement.setAttribute("type", param.getType()); + paramElement.setAttribute("value", param.getValue()); + } + + return document; + } + + @SuppressWarnings("nls") + @Override + public void fromDom(Document document) { + + Element rootElement = document.getDocumentElement(); + + String id = rootElement.getAttribute("id"); + String name = rootElement.getAttribute("name"); + String type = rootElement.getAttribute("type"); + + MyModel resource = new MyModel(id, name, type); + + NodeList children = rootElement.getChildNodes(); + for (int i = 0; i < children.getLength(); i++) { + Node item = children.item(i); + if (!item.getNodeName().equals("Parameter")) + continue; + + Element paramElement = (Element) item; + String paramId = paramElement.getAttribute("id"); + String paramName = paramElement.getAttribute("name"); + String paramType = paramElement.getAttribute("type"); + String paramValue = paramElement.getAttribute("value"); + + MyParameter param = new MyParameter(paramId, paramName, paramType, paramValue); + resource.addParameter(param); + } + + this.resource = resource; + } +} \ No newline at end of file diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelParserFactory.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelParserFactory.java new file mode 100644 index 000000000..0760861fb --- /dev/null +++ b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelParserFactory.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.test.impl; + +import ch.eitchnet.xmlpers.api.DomParser; +import ch.eitchnet.xmlpers.api.ParserFactory; +import ch.eitchnet.xmlpers.api.SaxParser; +import ch.eitchnet.xmlpers.test.model.MyModel; + +public class MyModelParserFactory implements ParserFactory { + + @Override + public DomParser getDomParser() { + return new MyModelDomParser(); + } + + @Override + public SaxParser getSaxParser() { + return new MyModelSaxParser(); + } +} \ No newline at end of file diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelSaxParser.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelSaxParser.java new file mode 100644 index 000000000..0cb2f0780 --- /dev/null +++ b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/MyModelSaxParser.java @@ -0,0 +1,91 @@ +/* + * 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.test.impl; + +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import ch.eitchnet.xmlpers.api.SaxParser; +import ch.eitchnet.xmlpers.test.model.MyModel; +import ch.eitchnet.xmlpers.test.model.MyParameter; + +class MyModelSaxParser extends DefaultHandler implements SaxParser { + + private MyModel resource; + + @Override + public MyModel getObject() { + return this.resource; + } + + @Override + public void setObject(MyModel object) { + this.resource = object; + } + + @Override + public DefaultHandler getDefaultHandler() { + return this; + } + + @SuppressWarnings("nls") + @Override + public void write(XMLStreamWriter writer) throws XMLStreamException { + + writer.writeStartElement("Resource"); + writer.writeAttribute("id", this.resource.getId()); + writer.writeAttribute("name", this.resource.getName()); + writer.writeAttribute("type", this.resource.getType()); + for (String paramId : this.resource.getParameterKeySet()) { + MyParameter param = this.resource.getParameterBy(paramId); + writer.writeEmptyElement("Parameter"); + writer.writeAttribute("id", param.getId()); + writer.writeAttribute("name", param.getName()); + writer.writeAttribute("type", param.getType()); + writer.writeAttribute("value", param.getValue()); + } + writer.writeEndElement(); + } + + @SuppressWarnings("nls") + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + + switch (qName) { + case "Resource": + String id = attributes.getValue("id"); + String name = attributes.getValue("name"); + String type = attributes.getValue("type"); + MyModel resource = new MyModel(id, name, type); + this.resource = resource; + break; + case "Parameter": + id = attributes.getValue("id"); + name = attributes.getValue("name"); + type = attributes.getValue("type"); + String value = attributes.getValue("value"); + MyParameter param = new MyParameter(id, name, type, value); + this.resource.addParameter(param); + break; + default: + throw new IllegalArgumentException("The element '" + qName + "' is unhandled!"); + } + } +} \ No newline at end of file diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/TestConstants.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/TestConstants.java new file mode 100644 index 000000000..cfa62b831 --- /dev/null +++ b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/impl/TestConstants.java @@ -0,0 +1,26 @@ +/* + * 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.test.impl; + +/** + * @author Robert von Burg + * + */ +public class TestConstants { + + public static final String TYPE_RES = "Resource"; //$NON-NLS-1$ + public static final String TYPE_BOOK = "Book"; //$NON-NLS-1$ +} diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/Book.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/Book.java new file mode 100644 index 000000000..536be946c --- /dev/null +++ b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/Book.java @@ -0,0 +1,121 @@ +/* + * 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.test.model; + +/** + * @author Robert von Burg + * + */ +public class Book { + + private final Long id; + private String title; + private String author; + private String press; + private double price; + + /** + * @param id + * @param title + * @param author + * @param press + * @param price + */ + public Book(Long id, String title, String author, String press, double price) { + super(); + this.id = id; + this.title = title; + this.author = author; + this.press = press; + this.price = price; + } + + /** + * + */ + public Book(Long id) { + if (id == null) + throw new IllegalArgumentException("Id may not be null!"); //$NON-NLS-1$ + this.id = id; + } + + /** + * @return the id + */ + public Long getId() { + return this.id; + } + + /** + * @return the title + */ + public String getTitle() { + return this.title; + } + + /** + * @param title + * the title to set + */ + public void setTitle(String title) { + this.title = title; + } + + /** + * @return the author + */ + public String getAuthor() { + return this.author; + } + + /** + * @param author + * the author to set + */ + public void setAuthor(String author) { + this.author = author; + } + + /** + * @return the press + */ + public String getPress() { + return this.press; + } + + /** + * @param press + * the press to set + */ + public void setPress(String press) { + this.press = press; + } + + /** + * @return the price + */ + public double getPrice() { + return this.price; + } + + /** + * @param price + * the price to set + */ + public void setPrice(double price) { + this.price = price; + } +} diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java new file mode 100644 index 000000000..be4d9ff78 --- /dev/null +++ b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/ModelBuilder.java @@ -0,0 +1,123 @@ +/* + * 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.test.model; + +import org.junit.Assert; + +/** + * @author Robert von Burg + * + */ +@SuppressWarnings("nls") +public class ModelBuilder { + + public static final String RES_TYPE = "@subType"; + public static final String RES_TYPE_INEXISTANT = "@inexistant"; + public static final String RES_NAME = "@name"; + public static final String RES_NAME_MODIFIED = "@name_modified"; + public static final String RES_ID = "@id"; + + public static final String PARAM_TYPE = "@paramType"; + public static final String PARAM_NAME = "@paramName"; + public static final String PARAM_ID = "@paramId"; + public static final String PARAM_VALUE_1 = "@paramValue1"; + public static final String PARAM_VALUE_2 = "@paramValue2"; + + public static final long BOOK_ID = 10L; + public static final String BOOK_TITLE = "Nick Hornby"; + public static final String BOOK_AUTHOR = "A long way down"; + public static final String BOOK_PRESS_1 = "Some press"; + public static final String BOOK_PRESS_2 = "Another press"; + public static final double BOOK_PRICE = 45.55D; + + public static MyModel createResource() { + return createResource(RES_ID, RES_NAME, RES_TYPE); + } + + public static MyModel createResource(String id) { + return createResource(id, RES_NAME, RES_TYPE); + } + + public static MyModel createResource(String id, String name, String type) { + MyModel resource = new MyModel(id, name, type); + MyParameter param = new MyParameter(PARAM_ID, PARAM_NAME, PARAM_TYPE, PARAM_VALUE_1); + resource.addParameter(param); + return resource; + } + + public static void updateResource(MyModel resource) { + resource.setName(RES_NAME_MODIFIED); + resource.getParameterBy(PARAM_ID).setValue(PARAM_VALUE_2); + } + + public static Book createBook() { + Book book = new Book(BOOK_ID, BOOK_TITLE, BOOK_AUTHOR, BOOK_PRESS_1, BOOK_PRICE); + return book; + } + + public static Book createBook(long id, String title, String author, String press, double price) { + Book book = new Book(id, title, author, press, price); + return book; + } + + public static void updateBook(Book book) { + book.setPress(BOOK_PRESS_2); + } + + public static void assertBook(Book book) { + Assert.assertNotNull(book); + Assert.assertEquals(BOOK_ID, book.getId().longValue()); + Assert.assertEquals(BOOK_TITLE, book.getTitle()); + Assert.assertEquals(BOOK_AUTHOR, book.getAuthor()); + Assert.assertEquals(BOOK_PRESS_1, book.getPress()); + Assert.assertEquals(BOOK_PRICE, book.getPrice(), 0.0); + } + + public static void assertBookUpdated(Book book) { + Assert.assertNotNull(book); + Assert.assertEquals(BOOK_ID, book.getId().longValue()); + Assert.assertEquals(BOOK_TITLE, book.getTitle()); + Assert.assertEquals(BOOK_AUTHOR, book.getAuthor()); + Assert.assertEquals(BOOK_PRESS_2, book.getPress()); + Assert.assertEquals(BOOK_PRICE, book.getPrice(), 0.0); + } + + public static void assertResource(MyModel resource) { + Assert.assertNotNull(resource); + Assert.assertEquals(RES_ID, resource.getId()); + Assert.assertEquals(RES_NAME, resource.getName()); + Assert.assertEquals(RES_TYPE, resource.getType()); + MyParameter param = resource.getParameterBy(PARAM_ID); + Assert.assertNotNull(param); + Assert.assertEquals(PARAM_ID, param.getId()); + Assert.assertEquals(PARAM_NAME, param.getName()); + Assert.assertEquals(PARAM_TYPE, param.getType()); + Assert.assertEquals(PARAM_VALUE_1, param.getValue()); + } + + public static void assertResourceUpdated(MyModel resource) { + Assert.assertNotNull(resource); + Assert.assertEquals(RES_ID, resource.getId()); + Assert.assertEquals(RES_NAME_MODIFIED, resource.getName()); + Assert.assertEquals(RES_TYPE, resource.getType()); + MyParameter param = resource.getParameterBy(PARAM_ID); + Assert.assertNotNull(param); + Assert.assertEquals(PARAM_ID, param.getId()); + Assert.assertEquals(PARAM_NAME, param.getName()); + Assert.assertEquals(PARAM_TYPE, param.getType()); + Assert.assertEquals(PARAM_VALUE_2, param.getValue()); + } +} diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/MyModel.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/MyModel.java new file mode 100644 index 000000000..f8f90058a --- /dev/null +++ b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/MyModel.java @@ -0,0 +1,124 @@ +/* + * 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.test.model; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +public class MyModel { + + private String id; + private String name; + private String type; + private Map parameters = new HashMap<>(); + + /** + * + */ + public MyModel() { + // empty constructor + } + + /** + * @param id + * @param name + * @param type + */ + public MyModel(String id, String name, String type) { + super(); + this.id = id; + this.name = name; + this.type = type; + } + + @SuppressWarnings("nls") + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Resource [id="); + builder.append(this.id); + builder.append(", name="); + builder.append(this.name); + builder.append(", type="); + builder.append(this.type); + builder.append(", parameters="); + for (Entry param : this.parameters.entrySet()) { + builder.append("\n"); + builder.append(" " + param.getKey() + " = " + param.getValue()); + } + builder.append("]"); + return builder.toString(); + } + + /** + * @return the id + */ + public String getId() { + return this.id; + } + + /** + * @param id + * the id to set + */ + public void setId(String id) { + this.id = id; + } + + /** + * @return the name + */ + public String getName() { + return this.name; + } + + /** + * @param name + * the name to set + */ + public void setName(String name) { + this.name = name; + } + + /** + * @return the type + */ + public String getType() { + return this.type; + } + + /** + * @param type + * the type to set + */ + public void setType(String type) { + this.type = type; + } + + public void addParameter(MyParameter parameter) { + this.parameters.put(parameter.getId(), parameter); + } + + public Set getParameterKeySet() { + return this.parameters.keySet(); + } + + public MyParameter getParameterBy(String id) { + return this.parameters.get(id); + } +} diff --git a/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/MyParameter.java b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/MyParameter.java new file mode 100644 index 000000000..d4ca1f189 --- /dev/null +++ b/ch.eitchnet.xmlpers/src/test/java/ch/eitchnet/xmlpers/test/model/MyParameter.java @@ -0,0 +1,121 @@ +/* + * 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.test.model; + +public class MyParameter { + + private String id; + private String name; + private String type; + private String value; + + /** + * + */ + public MyParameter() { + // empty constructor + } + + /** + * @param id + * @param name + * @param type + * @param value + */ + public MyParameter(String id, String name, String type, String value) { + super(); + this.id = id; + this.name = name; + this.type = type; + this.value = value; + } + + @SuppressWarnings("nls") + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Parameter [id="); + builder.append(this.id); + builder.append(", name="); + builder.append(this.name); + builder.append(", type="); + builder.append(this.type); + builder.append(", value="); + builder.append(this.value); + builder.append("]"); + return builder.toString(); + } + + /** + * @return the id + */ + public String getId() { + return this.id; + } + + /** + * @param id + * the id to set + */ + public void setId(String id) { + this.id = id; + } + + /** + * @return the name + */ + public String getName() { + return this.name; + } + + /** + * @param name + * the name to set + */ + public void setName(String name) { + this.name = name; + } + + /** + * @return the type + */ + public String getType() { + return this.type; + } + + /** + * @param type + * the type to set + */ + public void setType(String type) { + this.type = type; + } + + /** + * @return the value + */ + public String getValue() { + return this.value; + } + + /** + * @param value + * the value to set + */ + public void setValue(String value) { + this.value = value; + } +} \ No newline at end of file diff --git a/ch.eitchnet.xmlpers/src/test/resources/log4j.xml b/ch.eitchnet.xmlpers/src/test/resources/log4j.xml new file mode 100644 index 000000000..f223bc5cb --- /dev/null +++ b/ch.eitchnet.xmlpers/src/test/resources/log4j.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +