From b5ddd621453a07f7530733c5834715cd457770db Mon Sep 17 00:00:00 2001 From: Kishalay Pandey Date: Tue, 26 Mar 2024 23:57:17 +0530 Subject: [PATCH] feat: Implemenation of the Serialized Lob Pattern (#2795) * #1596:Adding module for SLOB Pattern * #1596:Adding Interface for Serializers * #1596:Adding Objects * #1596:Adding BLOB Based Serializer * #1596:Implemented Clob Based Serializer * #1596:Implemented Service for DB operations * #1596:Implemented basic flow using Clob Serializer * #1596:Added H2 DB Dependency * #1596:Added Java Doc Stubs * Updating Objects * #1596:Completed Clob Serializer along with Tests * #1596:Completed Slob Serializer along with Tests * #1596:Completed Blob Serializer along with Tests * #1596:Updated DatabaseService to handle Blob and Clob Data * #1596:Added UML and updated README.md * #1596:Updated data types for DB * #1596:Code Style Formatting Cnames * Adding Java Docs * #1596:Reformatted as per Code Style * #1596:Updating Java Doc * #1596:Updating Main function to handle both Serializers * #1596:Updated Java Docs * #1596:Updated Java Docs * #1596:Updated Java Docs and formatting * #1596:Updated Java Docs and formatting * #1596:Adding Java Docs * #1596:Reformatted as per Code Style * #1596:Updating Java Doc * #1596:Updating Main function to handle both Serializers * #1596:Updated Java Docs * #1596:Updated Java Docs * #1596:Updated Java Docs and formatting * #1596:Updated Java Docs and formatting * #1596:Updated Java Docs and formatting * #1596:Updated Java Docs and formatting * #1596:Updated README.md * #1596:Updated Java Docs and README.md * #1596:Updated Extension * #1596:Updated README.md * #1596:Updated Documentation as per review comments * #1596:Updated Documentation as per review comments * #1596: Updated README.md * #1596:Updated Documentation as per review comments * #1596: Added SLOB Module --------- Co-authored-by: SHRADDHAP18 <87650482+SHRADDHAP18@users.noreply.github.com> --- pom.xml | 1 + slob/README.md | 354 ++++++++++++++++++ slob/etc/slob.urm.png | Bin 0 -> 40078 bytes slob/etc/slob.urm.uml | 49 +++ slob/pom.xml | 78 ++++ slob/src/main/java/com/iluwatar/slob/App.java | 144 +++++++ .../slob/dbservice/DatabaseService.java | 157 ++++++++ .../java/com/iluwatar/slob/lob/Animal.java | 131 +++++++ .../java/com/iluwatar/slob/lob/Forest.java | 121 ++++++ .../java/com/iluwatar/slob/lob/Plant.java | 80 ++++ .../slob/serializers/BlobSerializer.java | 83 ++++ .../slob/serializers/ClobSerializer.java | 107 ++++++ .../slob/serializers/LobSerializer.java | 115 ++++++ .../test/java/com/iluwatar/slob/AppTest.java | 143 +++++++ 14 files changed, 1563 insertions(+) create mode 100644 slob/README.md create mode 100644 slob/etc/slob.urm.png create mode 100644 slob/etc/slob.urm.uml create mode 100644 slob/pom.xml create mode 100644 slob/src/main/java/com/iluwatar/slob/App.java create mode 100644 slob/src/main/java/com/iluwatar/slob/dbservice/DatabaseService.java create mode 100644 slob/src/main/java/com/iluwatar/slob/lob/Animal.java create mode 100644 slob/src/main/java/com/iluwatar/slob/lob/Forest.java create mode 100644 slob/src/main/java/com/iluwatar/slob/lob/Plant.java create mode 100644 slob/src/main/java/com/iluwatar/slob/serializers/BlobSerializer.java create mode 100644 slob/src/main/java/com/iluwatar/slob/serializers/ClobSerializer.java create mode 100644 slob/src/main/java/com/iluwatar/slob/serializers/LobSerializer.java create mode 100644 slob/src/test/java/com/iluwatar/slob/AppTest.java diff --git a/pom.xml b/pom.xml index 44178fd1a..2e1b3312b 100644 --- a/pom.xml +++ b/pom.xml @@ -215,6 +215,7 @@ single-table-inheritance dynamic-proxy gateway + slob diff --git a/slob/README.md b/slob/README.md new file mode 100644 index 000000000..13ef6cb63 --- /dev/null +++ b/slob/README.md @@ -0,0 +1,354 @@ +--- +title: Serialized LOB +category: Structural +language: en +tag: + - Data access +--- + +## Intent + +* Object models often contain complicated graphs of small objects. Much of the information in these + structures isn’t in the objects but in the links between them. +* Objects don’t have to be persisted as table rows related to each other. +* Another form of persistence is serialization, where a whole graph of objects is written out as a + single large object (LOB) in a table. + +## Explanation + +**In plain words** + +> The Forest here represents the object graph. +> A forest contains animals and plants. As is in real life the forest object contains plants and +> animals where some animals eat other animals and some eat only plants. +> * These relationships are maintained in the Forest Object. +> * There are 2 types of Serializers available ClobSerializer and BlobSerializer. +> * ClobSerializer uses character or textual based serialization, here the Object graph is converted +> * to XML and then stored as text in DB. +> * BlobSerializer uses binary data for serialization, here the Object graph is converted to Byte +> * Array and then stored as a blob in DB. + +**Programmatic Example** + +* Here is the `Animal` class. It represents the Animal object in the Forest. It contains the name of + the animals in the forest and details of what they eat from the forest plants/animals or both. + +```java +/** + * Creates an object Forest which contains animals and plants as its constituents. Animals may eat + * plants or other animals in the forest. + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Forest implements Serializable { + + private String name; + private final Set animals = new HashSet<>(); + private final Set plants = new HashSet<>(); + + /** + * Provides the representation of Forest in XML form. + * + * @return XML Element + */ + public Element toXmlElement() throws ParserConfigurationException { + Document xmlDoc = getXmlDoc(); + + Element forestXml = xmlDoc.createElement("Forest"); + forestXml.setAttribute("name", name); + + Element animalsXml = xmlDoc.createElement("Animals"); + for (Animal animal : animals) { + Element animalXml = animal.toXmlElement(xmlDoc); + animalsXml.appendChild(animalXml); + } + forestXml.appendChild(animalsXml); + + Element plantsXml = xmlDoc.createElement("Plants"); + for (Plant plant : plants) { + Element plantXml = plant.toXmlElement(xmlDoc); + plantsXml.appendChild(plantXml); + } + forestXml.appendChild(plantsXml); + return forestXml; + } + + /** + * Returns XMLDoc to use for XML creation. + * + * @return XML DOC Object + * @throws ParserConfigurationException {@inheritDoc} + */ + private Document getXmlDoc() throws ParserConfigurationException { + return DocumentBuilderFactory.newDefaultInstance().newDocumentBuilder().newDocument(); + } + + /** + * Parses the Forest Object from the input XML Document. + * + * @param document the XML document from which the Forest is to be parsed + */ + public void createObjectFromXml(Document document) { + name = document.getDocumentElement().getAttribute("name"); + NodeList nodeList = document.getElementsByTagName("*"); + iterateXmlForAnimalAndPlants(nodeList, animals, plants); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("\n"); + sb.append("Forest Name = ").append(name).append("\n"); + sb.append("Animals found in the ").append(name).append(" Forest: \n"); + for (Animal animal : animals) { + sb.append("\n--------------------------\n"); + sb.append(animal.toString()); + sb.append("\n--------------------------\n"); + } + sb.append("\n"); + sb.append("Plants in the ").append(name).append(" Forest: \n"); + for (Plant plant : plants) { + sb.append("\n--------------------------\n"); + sb.append(plant.toString()); + sb.append("\n--------------------------\n"); + } + return sb.toString(); + } +} +``` + +* Here is the `LobSerializer` abstract class. It provides the specification to serialize and + deserialize the input object and persist and load that object into a DB. + +```java +/** + * A LobSerializer can be used to create an instance of a serializer which can serialize and + * deserialize an object and persist and load that object into a DB. from their Binary + * Representation. + */ +public abstract class LobSerializer implements Serializable, Closeable { + + private final transient DatabaseService databaseService; + + /** + * Constructor initializes {@link LobSerializer#databaseService}. + * + * @param dataTypeDb Input provides type of Data to be stored by the Data Base Service + * @throws SQLException If any issue occurs during instantiation of DB Service or during startup. + */ + protected LobSerializer(String dataTypeDb) throws SQLException { + databaseService = new DatabaseService(dataTypeDb); + databaseService.startupService(); + } + + /** + * Provides the specification to Serialize the input object. + * + * @param toSerialize Input Object to serialize + * @return Serialized Object + * @throws ParserConfigurationException if any issue occurs during parsing of input object + * @throws TransformerException if any issue occurs during Transformation + * @throws IOException if any issues occur during reading object + */ + public abstract Object serialize(Forest toSerialize) + throws ParserConfigurationException, TransformerException, IOException; + + /** + * Saves the object to DB with the provided ID. + * + * @param id key to be sent to DB service + * @param name Object name to store in DB + * @param object Object to store in DB + * @return ID with which the object is stored in DB + * @throws SQLException if any issue occurs while saving to DB + */ + public int persistToDb(int id, String name, Object object) throws SQLException { + databaseService.insert(id, name, object); + return id; + } + + /** + * Loads the object from db using the ID and column name. + * + * @param id to query the DB + * @param columnName column from which object is to be extracted + * @return Object from DB + * @throws SQLException if any issue occurs while loading from DB + */ + public Object loadFromDb(int id, String columnName) throws SQLException { + return databaseService.select(id, columnName); + } + + /** + * Provides the specification to Deserialize the input object. + * + * @param toDeserialize object to deserialize + * @return Deserialized Object + * @throws ParserConfigurationException If issue occurs during parsing of input object + * @throws IOException if any issues occur during reading object + * @throws SAXException if any issues occur during reading object for XML parsing + */ + public abstract Forest deSerialize(Object toDeserialize) + throws ParserConfigurationException, IOException, SAXException, ClassNotFoundException; + + @Override + public void close() { + try { + databaseService.shutDownService(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } +} +``` + +* Here is the `ClobSerializer` class. It extends the `LobSerializer` abstract class and provides the + implementation to serialize and deserialize the input object and persist and load that object into + a DB using ClobSerializer. +* Objects are serialized using character or textual based serialization + using XML to represent the object graph. + +```java + +/** + * Creates a Serializer that uses Character based serialization and deserialization of objects graph + * to and from XML Representation. + */ +public class ClobSerializer extends LobSerializer { + + public static final String TYPE_OF_DATA_FOR_DB = "TEXT"; + + public ClobSerializer() { + super(TYPE_OF_DATA_FOR_DB); + } + + /** + * Converts the input node to its XML String Representation. + * + * @param node XML Node that is to be converted to string + * @return String representation of XML parsed from the Node + * @throws TransformerException If any issues occur in Transformation from Node to XML + */ + private static String elementToXmlString(Element node) throws TransformerException { + StringWriter sw = new StringWriter(); + Transformer t = TransformerFactory.newDefaultInstance().newTransformer(); + t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); + t.setOutputProperty(OutputKeys.INDENT, "yes"); + t.transform(new DOMSource(node), new StreamResult(sw)); + return sw.toString(); + } + + /** + * Serializes the input object graph to its XML Representation using DOM Elements. + * + * @param forest Object which is to be serialized + * @return Serialized object + * @throws ParserConfigurationException If any issues occur in parsing input object + * @throws TransformerException If any issues occur in Transformation from Node to XML + */ + @Override + public Object serialize(Forest forest) throws ParserConfigurationException, TransformerException { + Element xmlElement = forest.toXmlElement(); + return elementToXmlString(xmlElement); + } + + /** + * Deserializes the input XML string using DOM Parser and return its Object Graph Representation. + * + * @param toDeserialize Input Object to De-serialize + * @return Deserialized Object + * @throws ParserConfigurationException If any issues occur in parsing input object + * @throws IOException if any issues occur during reading object + * @throws SAXException If any issues occur in Transformation from Node to XML + */ + @Override + public Forest deSerialize(Object toDeserialize) + throws ParserConfigurationException, IOException, SAXException { + DocumentBuilder documentBuilder = DocumentBuilderFactory.newDefaultInstance() + .newDocumentBuilder(); + var stream = new ByteArrayInputStream(toDeserialize.toString().getBytes()); + Document parsed = documentBuilder.parse(stream); + Forest forest = new Forest(); + forest.createObjectFromXml(parsed); + return forest; + } +} +``` + +* Here is the `SlobSerializer` class. It extends the `LobSerializer` abstract class and provides the + implementation to serialize and deserialize the input object and persist and load that object into + a DB using ClobSerializer. +* Objects are serialized using binary data based serialization objects a persisted as a BINARY/BLOB + in DB. + +```java +/** + * Creates a Serializer that uses Binary serialization and deserialization of objects graph to and + * from their Binary Representation. + */ +public class BlobSerializer extends LobSerializer { + + public static final String TYPE_OF_DATA_FOR_DB = "BINARY"; + + public BlobSerializer() throws SQLException { + super(TYPE_OF_DATA_FOR_DB); + } + + /** + * Serializes the input object graph to its Binary Representation using Object Stream. + * + * @param toSerialize Object which is to be serialized + * @return Serialized object + * @throws IOException {@inheritDoc} + */ + @Override + public Object serialize(Forest toSerialize) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(toSerialize); + oos.close(); + return new ByteArrayInputStream(baos.toByteArray()); + } + + /** + * Deserializes the input Byte Array Stream using Object Stream and return its Object Graph + * Representation. + * + * @param toDeserialize Input Object to De-serialize + * @return Deserialized Object + * @throws ClassNotFoundException {@inheritDoc} + * @throws IOException {@inheritDoc} + */ + @Override + public Forest deSerialize(Object toDeserialize) throws IOException, ClassNotFoundException { + InputStream bis = (InputStream) toDeserialize; + Forest forest; + try (ObjectInput in = new ObjectInputStream(bis)) { + forest = (Forest) in.readObject(); + } + return forest; + } +} +``` + +## Class diagram + +![alt text](./etc/slob.urm.png "Serialized LOB") + +## Applicability + +* This pattern works best when you can chop out a piece of the object model and use it to represent + the LOB. +* Think of a LOB as a way to take a bunch of objects that aren’t likely to be queried from any SQL + route outside the application. +* This graph can then be hooked into the SQL schema. Serialized LOB works poorly when you have + objects outside the LOB reference objects buried in it. +* Serialized LOB isn’t considered as often as it might be. XML makes it much more attractive since + it yields an easy-to-implement textual approach. +* Its main disadvantage is that you can’t query the structure using SQL. +* SQL extensions appear to get at XML data within a field, but that’s still not the same (or + portable). + +## Credits + +* [Serialized LOB](https://martinfowler.com/eaaCatalog/serializedLOB.html) by Martin Fowler diff --git a/slob/etc/slob.urm.png b/slob/etc/slob.urm.png new file mode 100644 index 0000000000000000000000000000000000000000..e852dfb21d0858e2b10fb712bce5a6606e0d267b GIT binary patch literal 40078 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 TXR63eH!w!jW)aO{m6-sq}){Pd|Xk=6F-9LzB8 zUCBP?F>gs~DsI7HixmG_b}P>HMirqdQ%m+MEFilCv!o!>w=-1Nh}MLj*M9lFSQKy% znMqx^{BT=m+Z|r1+t0pMm)5)3n} z_kj}^lloq%cD{~uu$EG&6j{|a7JwM_1LN7JF_+Zg+1@X_l3GW;$_UH@=)zaT8)$Tq~0IOq(=72hB8IrcB@Hmf`vJUGOKU zsMyKM8o70EEr?L2mT!vGnX;JPyB>kG%GA0)#!{{%#AByfp`G4s9Z7IPnRlZ1{u$#M zp#Re3E%n@OQ<=CCkuB`KxH{kMbxMTvY-pCY`kPv8fq}ba2oZ>po|>9e`-GyN1u>(^6|x|D==)YsRef+Ur_-j?mw zG%JVfDtXJCy&9n=&ukLsG~cE2a>UV0y^l`s-zXFwVDef?qsP;IA!h%3#k3n($?{Zv zEmi+=Z*sq*+`k=L-=7fo_jz7)t{%Ug5{ZU_s9XJZd5eDa&!Jl?epB$krS_77C4R_f zk^cMHzs&m5cX$0iJ8pfny^WMfv?|vK$qfug&81sf&*)KE=QqR1HOpE3bO>I~>_qsI z0p2-NA<5uoF6WC&eQlMsDgB5z0Wd#4wb0`F~pV!-UWb8yOeog zvns}_m-GJeQSMzj;R8Op$DqgyDUa9`eGXiWab#S9nHn31vLOavu3$@{;S=#Pw_3Stj<8p*0J>nKx~5=#Ey3((k9g~BwGzV z2&K%LiG9f&v-tQL&XeR_6rI#OCmKvE9;9Mm;TL~uE-1i56k;Az-b#=5s5R}%syO)c z5;7scOFKJ?-A6m*->Bj2UcLjBIFxjE8Q-um6%rD%^!a(n9sGw~KxsMi_7-q_B_lDN zSDBjCMZFq=)4mYLgvKkB)N|2x$+$6bdZ_spO1Tmz=fB^pic$hPiC{Xj+#cV<(n_#g zEG^#y;94u9`&BRVJc{iXHvjw0%<6*lj)oyd+TI6yLzlY>1u9gJO?T=ML;VU9XTwH9 zHs_u3+Pj>Y3{GI-ZOqaO9)}?%#^4GHKK

mA2!mP{u5l9)S15t+zWj-|K)fu0!sI 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 rbp1(wZRBN)BvL*V`g`pz@Eb literal 0 HcmV?d00001 diff --git a/slob/etc/slob.urm.uml b/slob/etc/slob.urm.uml new file mode 100644 index 000000000..1d9de1e46 --- /dev/null +++ b/slob/etc/slob.urm.uml @@ -0,0 +1,49 @@ +@startuml + +!theme plain +top to bottom direction +skinparam linetype ortho + +class Animal { + - animalsEaten: Set + - name: String + - plantsEaten: Set + animalsEaten: Set + name: String + plantsEaten: Set +} +class App +class BlobSerializer +class ClobSerializer +class DatabaseService +class Forest { + - animals: Set + - name: String + - plants: Set + animals: Set + name: String + plants: Set + xmlDoc: Document +} +class LobSerializer +class Plant { + - name: String + - type: String + name: String + type: String +} + +Animal -[#595959,dashed]-> Plant : "«create»" +Animal "1" *-[#595959,plain]-> "plantsEaten\n*" Plant +App -[#595959,dashed]-> Animal : "«create»" +App -[#595959,dashed]-> ClobSerializer : "«create»" +App -[#595959,dashed]-> Forest : "«create»" +App -[#595959,dashed]-> Plant : "«create»" +BlobSerializer -[#000082,plain]-^ LobSerializer +ClobSerializer -[#595959,dashed]-> Forest : "«create»" +ClobSerializer -[#000082,plain]-^ LobSerializer +Forest "1" *-[#595959,plain]-> "animals\n*" Animal +Forest "1" *-[#595959,plain]-> "plants\n*" Plant +LobSerializer "1" *-[#595959,plain]-> "databaseService\n1" DatabaseService +LobSerializer -[#595959,dashed]-> DatabaseService : "«create»" +@enduml diff --git a/slob/pom.xml b/slob/pom.xml new file mode 100644 index 000000000..5f95d4c1b --- /dev/null +++ b/slob/pom.xml @@ -0,0 +1,78 @@ + + + + + slob + 4.0.0 + + java-design-patterns + com.iluwatar + 1.26.0-SNAPSHOT + + + + + + maven-assembly-plugin + + + + + + com.iluwatar.slob.App + + + + + + org.apache.maven.plugins + + + + + + + junit-jupiter-engine + org.junit.jupiter + test + + + h2 + com.h2database + 2.2.220 + + + lombok + org.projectlombok + provided + + + + diff --git a/slob/src/main/java/com/iluwatar/slob/App.java b/slob/src/main/java/com/iluwatar/slob/App.java new file mode 100644 index 000000000..743cc8293 --- /dev/null +++ b/slob/src/main/java/com/iluwatar/slob/App.java @@ -0,0 +1,144 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.slob; + +import com.iluwatar.slob.lob.Animal; +import com.iluwatar.slob.lob.Forest; +import com.iluwatar.slob.lob.Plant; +import com.iluwatar.slob.serializers.BlobSerializer; +import com.iluwatar.slob.serializers.ClobSerializer; +import com.iluwatar.slob.serializers.LobSerializer; +import java.io.IOException; +import java.sql.SQLException; +import java.util.Collections; +import java.util.Objects; +import java.util.Set; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xml.sax.SAXException; + +/** + * SLOB Application using {@link LobSerializer} and H2 DB. + */ +public class App { + + public static final String CLOB = "CLOB"; + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + /** + * Main entry point to program. + *

In the SLOB pattern, the object graph is serialized into a single large object (a BLOB or + * CLOB, for Binary Large Object or Character Large Object, respectively) and stored in the + * database. When the object graph needs to be retrieved, it is read from the database and + * deserialized back into the original object graph.

+ * + *

A Forest is created using {@link #createForest()} with Animals and Plants along with their + * respective relationships.

+ * + *

Creates a {@link LobSerializer} using the method + * {@link #createLobSerializer(String[])}.

+ * + *

Once created the serializer is passed to the + * {@link #executeSerializer(Forest, LobSerializer)} which handles the serialization, + * deserialization and persisting and loading from DB.

+ * + * @param args if first arg is CLOB then ClobSerializer is used else BlobSerializer is used. + */ + public static void main(String[] args) throws SQLException { + Forest forest = createForest(); + LobSerializer serializer = createLobSerializer(args); + executeSerializer(forest, serializer); + } + + /** + *

Creates a {@link LobSerializer} on the basis of input args.

+ *

If input args are not empty and the value equals {@link App#CLOB} then a + * {@link ClobSerializer} is created else a {@link BlobSerializer} is created.

+ * + * @param args if first arg is {@link App#CLOB} then ClobSerializer is instantiated else + * BlobSerializer is instantiated. + */ + private static LobSerializer createLobSerializer(String[] args) throws SQLException { + LobSerializer serializer; + if (args.length > 0 && Objects.equals(args[0], CLOB)) { + serializer = new ClobSerializer(); + } else { + serializer = new BlobSerializer(); + } + return serializer; + } + + /** + * Creates a Forest with {@link Animal} and {@link Plant} along with their respective + * relationships. + * + *

The method creates a {@link Forest} with 2 Plants Grass and Oak of type Herb and tree + * respectively.

+ * + *

It also creates 3 animals Zebra and Buffalo which eat the plant grass. Lion consumes the + * Zebra and the Buffalo.

+ * + *

With the above animals and plants and their relationships a forest + * object is created which represents the Object Graph.

+ * + * @return Forest Object + */ + private static Forest createForest() { + Plant grass = new Plant("Grass", "Herb"); + Plant oak = new Plant("Oak", "Tree"); + + Animal zebra = new Animal("Zebra", Set.of(grass), Collections.emptySet()); + Animal buffalo = new Animal("Buffalo", Set.of(grass), Collections.emptySet()); + Animal lion = new Animal("Lion", Collections.emptySet(), Set.of(zebra, buffalo)); + + return new Forest("Amazon", Set.of(lion, buffalo, zebra), Set.of(grass, oak)); + } + + /** + * Serialize the input object using the input serializer and persist to DB. After this it loads + * the same object back from DB and deserializes using the same serializer. + * + * @param forest Object to Serialize and Persist + * @param lobSerializer Serializer to Serialize and Deserialize Object + */ + private static void executeSerializer(Forest forest, LobSerializer lobSerializer) { + try (LobSerializer serializer = lobSerializer) { + + Object serialized = serializer.serialize(forest); + int id = serializer.persistToDb(1, forest.getName(), serialized); + + Object fromDb = serializer.loadFromDb(id, Forest.class.getSimpleName()); + Forest forestFromDb = serializer.deSerialize(fromDb); + + LOGGER.info(forestFromDb.toString()); + } catch (SQLException | IOException | TransformerException | ParserConfigurationException + | SAXException + | ClassNotFoundException e) { + throw new RuntimeException(e); + } + } +} diff --git a/slob/src/main/java/com/iluwatar/slob/dbservice/DatabaseService.java b/slob/src/main/java/com/iluwatar/slob/dbservice/DatabaseService.java new file mode 100644 index 000000000..ea54592c9 --- /dev/null +++ b/slob/src/main/java/com/iluwatar/slob/dbservice/DatabaseService.java @@ -0,0 +1,157 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.slob.dbservice; + +import java.sql.ResultSet; +import java.sql.SQLException; +import javax.sql.DataSource; +import lombok.extern.slf4j.Slf4j; +import org.h2.jdbcx.JdbcDataSource; + +/** + * Service to handle database operations. + */ +@Slf4j +public class DatabaseService { + + public static final String CREATE_BINARY_SCHEMA_DDL = + "CREATE TABLE IF NOT EXISTS FORESTS (ID NUMBER UNIQUE, NAME VARCHAR(30),FOREST VARBINARY)"; + public static final String CREATE_TEXT_SCHEMA_DDL = + "CREATE TABLE IF NOT EXISTS FORESTS (ID NUMBER UNIQUE, NAME VARCHAR(30),FOREST VARCHAR)"; + public static final String DELETE_SCHEMA_SQL = "DROP TABLE FORESTS IF EXISTS"; + public static final String BINARY_DATA = "BINARY"; + private static final String DB_URL = "jdbc:h2:~/test"; + private static final String INSERT = "insert into FORESTS (id,name, forest) values (?,?,?)"; + private static final String SELECT = "select FOREST from FORESTS where id = ?"; + private static final DataSource dataSource = createDataSource(); + public String dataTypeDb; + + /** + * Constructor initializes {@link DatabaseService#dataTypeDb}. + * + * @param dataTypeDb Type of data that is to be stored in DB can be 'TEXT' or 'BINARY'. + */ + public DatabaseService(String dataTypeDb) { + this.dataTypeDb = dataTypeDb; + } + + /** + * Initiates Data source. + * + * @return created data source + */ + private static DataSource createDataSource() { + var dataSource = new JdbcDataSource(); + dataSource.setURL(DB_URL); + return dataSource; + } + + /** + * Shutdown Sequence executes Query {@link DatabaseService#DELETE_SCHEMA_SQL}. + * + * @throws SQLException if any issue occurs while executing DROP Query + */ + public void shutDownService() + throws SQLException { + try (var connection = dataSource.getConnection(); + var statement = connection.createStatement()) { + statement.execute(DELETE_SCHEMA_SQL); + } + } + + /** + * Initaites startup sequence and executes the query + * {@link DatabaseService#CREATE_BINARY_SCHEMA_DDL} if {@link DatabaseService#dataTypeDb} is + * binary else will execute the query {@link DatabaseService#CREATE_TEXT_SCHEMA_DDL}. + * + * @throws SQLException if there are any issues during DDL execution + */ + public void startupService() + throws SQLException { + try (var connection = dataSource.getConnection(); + var statement = connection.createStatement()) { + if (dataTypeDb.equals("BINARY")) { + statement.execute(CREATE_BINARY_SCHEMA_DDL); + } else { + statement.execute(CREATE_TEXT_SCHEMA_DDL); + } + } + } + + /** + * Executes the insert query {@link DatabaseService#INSERT}. + * + * @param id with which row is to be inserted + * @param name name to be added in the row + * @param data object data to be saved in the row + * @throws SQLException if there are any issues in executing insert query + * {@link DatabaseService#INSERT} + */ + public void insert(int id, String name, Object data) + throws SQLException { + try (var connection = dataSource.getConnection(); + var insert = connection.prepareStatement(INSERT)) { + insert.setInt(1, id); + insert.setString(2, name); + insert.setObject(3, data); + insert.execute(); + } + } + + /** + * Runs the select query {@link DatabaseService#SELECT} form the result set returns an + * {@link java.io.InputStream} if {@link DatabaseService#dataTypeDb} is 'binary' else will return + * the object as a {@link String}. + * + * @param id with which row is to be selected + * @param columnsName column in which the object is stored + * @return object found from DB + * @throws SQLException if there are any issues in executing insert query * + * {@link DatabaseService#SELECT} + */ + public Object select(final long id, String columnsName) throws SQLException { + ResultSet resultSet = null; + try (var connection = dataSource.getConnection(); + var preparedStatement = + connection.prepareStatement(SELECT) + ) { + Object result = null; + preparedStatement.setLong(1, id); + resultSet = preparedStatement.executeQuery(); + while (resultSet.next()) { + if (dataTypeDb.equals(BINARY_DATA)) { + result = resultSet.getBinaryStream(columnsName); + } else { + result = resultSet.getString(columnsName); + } + } + return result; + } finally { + if (resultSet != null) { + resultSet.close(); + } + } + } +} diff --git a/slob/src/main/java/com/iluwatar/slob/lob/Animal.java b/slob/src/main/java/com/iluwatar/slob/lob/Animal.java new file mode 100644 index 000000000..770543cc5 --- /dev/null +++ b/slob/src/main/java/com/iluwatar/slob/lob/Animal.java @@ -0,0 +1,131 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.slob.lob; + +import java.io.Serializable; +import java.util.HashSet; +import java.util.Set; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +/** + * Creates an object Animal with a list of animals and/or plants it consumes. + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class Animal implements Serializable { + + private String name; + private Set plantsEaten = new HashSet<>(); + private Set animalsEaten = new HashSet<>(); + + /** + * Iterates over the input nodes recursively and adds new plants to {@link Animal#plantsEaten} or + * animals to {@link Animal#animalsEaten} found to input sets respectively. + * + * @param childNodes contains the XML Node containing the Forest + * @param animalsEaten set of Animals eaten + * @param plantsEaten set of Plants eaten + */ + protected static void iterateXmlForAnimalAndPlants(NodeList childNodes, Set animalsEaten, + Set plantsEaten) { + for (int i = 0; i < childNodes.getLength(); i++) { + Node child = childNodes.item(i); + if (child.getNodeType() == Node.ELEMENT_NODE) { + if (child.getNodeName().equals(Animal.class.getSimpleName())) { + Animal animalEaten = new Animal(); + animalEaten.createObjectFromXml(child); + animalsEaten.add(animalEaten); + } else if (child.getNodeName().equals(Plant.class.getSimpleName())) { + Plant plant = new Plant(); + plant.createObjectFromXml(child); + plantsEaten.add(plant); + } + } + } + } + + /** + * Provides XML Representation of the Animal. + * + * @param xmlDoc object to which the XML representation is to be written to + * @return XML Element contain the Animal representation + */ + public Element toXmlElement(Document xmlDoc) { + Element root = xmlDoc.createElement(Animal.class.getSimpleName()); + root.setAttribute("name", name); + for (Plant plant : plantsEaten) { + Element xmlElement = plant.toXmlElement(xmlDoc); + if (xmlElement != null) { + root.appendChild(xmlElement); + } + } + for (Animal animal : animalsEaten) { + Element xmlElement = animal.toXmlElement(xmlDoc); + if (xmlElement != null) { + root.appendChild(xmlElement); + } + } + xmlDoc.appendChild(root); + return (Element) xmlDoc.getFirstChild(); + } + + /** + * Parses the Animal Object from the input XML Node. + * + * @param node the XML Node from which the Animal Object is to be parsed + */ + public void createObjectFromXml(Node node) { + name = node.getAttributes().getNamedItem("name").getNodeValue(); + NodeList childNodes = node.getChildNodes(); + iterateXmlForAnimalAndPlants(childNodes, animalsEaten, plantsEaten); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("\nAnimal Name = ").append(name); + if (!animalsEaten.isEmpty()) { + sb.append("\n\tAnimals Eaten by ").append(name).append(": "); + } + for (Animal animal : animalsEaten) { + sb.append("\n\t\t").append(animal); + } + sb.append("\n"); + if (!plantsEaten.isEmpty()) { + sb.append("\n\tPlants Eaten by ").append(name).append(": "); + } + for (Plant plant : plantsEaten) { + sb.append("\n\t\t").append(plant); + } + return sb.toString(); + } +} diff --git a/slob/src/main/java/com/iluwatar/slob/lob/Forest.java b/slob/src/main/java/com/iluwatar/slob/lob/Forest.java new file mode 100644 index 000000000..1c14d55de --- /dev/null +++ b/slob/src/main/java/com/iluwatar/slob/lob/Forest.java @@ -0,0 +1,121 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.slob.lob; + +import static com.iluwatar.slob.lob.Animal.iterateXmlForAnimalAndPlants; + +import java.io.Serializable; +import java.util.HashSet; +import java.util.Set; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + +/** + * Creates an object Forest which contains animals and plants as its constituents. Animals may eat + * plants or other animals in the forest. + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Forest implements Serializable { + + private String name; + private Set animals = new HashSet<>(); + private Set plants = new HashSet<>(); + + /** + * Provides the representation of Forest in XML form. + * + * @return XML Element + */ + public Element toXmlElement() throws ParserConfigurationException { + Document xmlDoc = getXmlDoc(); + + Element forestXml = xmlDoc.createElement("Forest"); + forestXml.setAttribute("name", name); + + Element animalsXml = xmlDoc.createElement("Animals"); + for (Animal animal : animals) { + Element animalXml = animal.toXmlElement(xmlDoc); + animalsXml.appendChild(animalXml); + } + forestXml.appendChild(animalsXml); + + Element plantsXml = xmlDoc.createElement("Plants"); + for (Plant plant : plants) { + Element plantXml = plant.toXmlElement(xmlDoc); + plantsXml.appendChild(plantXml); + } + forestXml.appendChild(plantsXml); + return forestXml; + } + + /** + * Returns XMLDoc to use for XML creation. + * + * @return XML DOC Object + * @throws ParserConfigurationException {@inheritDoc} + */ + private Document getXmlDoc() throws ParserConfigurationException { + return DocumentBuilderFactory.newDefaultInstance().newDocumentBuilder().newDocument(); + } + + /** + * Parses the Forest Object from the input XML Document. + * + * @param document the XML document from which the Forest is to be parsed + */ + public void createObjectFromXml(Document document) { + name = document.getDocumentElement().getAttribute("name"); + NodeList nodeList = document.getElementsByTagName("*"); + iterateXmlForAnimalAndPlants(nodeList, animals, plants); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("\n"); + sb.append("Forest Name = ").append(name).append("\n"); + sb.append("Animals found in the ").append(name).append(" Forest: \n"); + for (Animal animal : animals) { + sb.append("\n--------------------------\n"); + sb.append(animal.toString()); + sb.append("\n--------------------------\n"); + } + sb.append("\n"); + sb.append("Plants in the ").append(name).append(" Forest: \n"); + for (Plant plant : plants) { + sb.append("\n--------------------------\n"); + sb.append(plant.toString()); + sb.append("\n--------------------------\n"); + } + return sb.toString(); + } +} diff --git a/slob/src/main/java/com/iluwatar/slob/lob/Plant.java b/slob/src/main/java/com/iluwatar/slob/lob/Plant.java new file mode 100644 index 000000000..20aff543e --- /dev/null +++ b/slob/src/main/java/com/iluwatar/slob/lob/Plant.java @@ -0,0 +1,80 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.slob.lob; + +import java.io.Serializable; +import java.util.StringJoiner; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +/** + * Creates an object Plant which contains its name and type. + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class Plant implements Serializable { + + private String name; + private String type; + + /** + * Provides XML Representation of the Plant. + * + * @param xmlDoc to which the XML representation is to be written to + * @return XML Element contain the Animal representation + */ + public Element toXmlElement(Document xmlDoc) { + Element root = xmlDoc.createElement(Plant.class.getSimpleName()); + root.setAttribute("name", name); + root.setAttribute("type", type); + xmlDoc.appendChild(root); + return xmlDoc.getDocumentElement(); + } + + /** + * Parses the Plant Object from the input XML Node. + * + * @param node the XML Node from which the Animal Object is to be parsed + */ + public void createObjectFromXml(Node node) { + NamedNodeMap attributes = node.getAttributes(); + name = attributes.getNamedItem("name").getNodeValue(); + type = attributes.getNamedItem("type").getNodeValue(); + } + + @Override + public String toString() { + StringJoiner stringJoiner = new StringJoiner(","); + stringJoiner.add("Name = " + name); + stringJoiner.add("Type = " + type); + return stringJoiner.toString(); + } +} diff --git a/slob/src/main/java/com/iluwatar/slob/serializers/BlobSerializer.java b/slob/src/main/java/com/iluwatar/slob/serializers/BlobSerializer.java new file mode 100644 index 000000000..c3858a84a --- /dev/null +++ b/slob/src/main/java/com/iluwatar/slob/serializers/BlobSerializer.java @@ -0,0 +1,83 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.slob.serializers; + +import com.iluwatar.slob.lob.Forest; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInput; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.sql.SQLException; + +/** + * Creates a Serializer that uses Binary serialization and deserialization of objects graph to and + * from their Binary Representation. + */ +public class BlobSerializer extends LobSerializer { + + public static final String TYPE_OF_DATA_FOR_DB = "BINARY"; + + public BlobSerializer() throws SQLException { + super(TYPE_OF_DATA_FOR_DB); + } + + /** + * Serializes the input object graph to its Binary Representation using Object Stream. + * + * @param toSerialize Object which is to be serialized + * @return Serialized object + * @throws IOException {@inheritDoc} + */ + @Override + public Object serialize(Forest toSerialize) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(toSerialize); + oos.close(); + return new ByteArrayInputStream(baos.toByteArray()); + } + + /** + * Deserializes the input Byte Array Stream using Object Stream and return its Object Graph + * Representation. + * + * @param toDeserialize Input Object to De-serialize + * @return Deserialized Object + * @throws ClassNotFoundException {@inheritDoc} + * @throws IOException {@inheritDoc} + */ + @Override + public Forest deSerialize(Object toDeserialize) throws IOException, ClassNotFoundException { + InputStream bis = (InputStream) toDeserialize; + Forest forest; + try (ObjectInput in = new ObjectInputStream(bis)) { + forest = (Forest) in.readObject(); + } + return forest; + } +} diff --git a/slob/src/main/java/com/iluwatar/slob/serializers/ClobSerializer.java b/slob/src/main/java/com/iluwatar/slob/serializers/ClobSerializer.java new file mode 100644 index 000000000..173444719 --- /dev/null +++ b/slob/src/main/java/com/iluwatar/slob/serializers/ClobSerializer.java @@ -0,0 +1,107 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.slob.serializers; + +import com.iluwatar.slob.lob.Forest; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.StringWriter; +import java.sql.SQLException; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; + +/** + * Creates a Serializer that uses Character based serialization and deserialization of objects graph + * to and from XML Representation. + */ +public class ClobSerializer extends LobSerializer { + + public static final String TYPE_OF_DATA_FOR_DB = "TEXT"; + + public ClobSerializer() throws SQLException { + super(TYPE_OF_DATA_FOR_DB); + } + + /** + * Converts the input node to its XML String Representation. + * + * @param node XML Node that is to be converted to string + * @return String representation of XML parsed from the Node + * @throws TransformerException If any issues occur in Transformation from Node to XML + */ + private static String elementToXmlString(Element node) throws TransformerException { + StringWriter sw = new StringWriter(); + Transformer t = TransformerFactory.newDefaultInstance().newTransformer(); + t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); + t.setOutputProperty(OutputKeys.INDENT, "yes"); + t.transform(new DOMSource(node), new StreamResult(sw)); + return sw.toString(); + } + + /** + * Serializes the input object graph to its XML Representation using DOM Elements. + * + * @param forest Object which is to be serialized + * @return Serialized object + * @throws ParserConfigurationException If any issues occur in parsing input object + * @throws TransformerException If any issues occur in Transformation from Node to XML + */ + @Override + public Object serialize(Forest forest) throws ParserConfigurationException, TransformerException { + Element xmlElement = forest.toXmlElement(); + return elementToXmlString(xmlElement); + } + + /** + * Deserializes the input XML string using DOM Parser and return its Object Graph Representation. + * + * @param toDeserialize Input Object to De-serialize + * @return Deserialized Object + * @throws ParserConfigurationException If any issues occur in parsing input object + * @throws IOException if any issues occur during reading object + * @throws SAXException If any issues occur in Transformation from Node to XML + */ + @Override + public Forest deSerialize(Object toDeserialize) + throws ParserConfigurationException, IOException, SAXException { + DocumentBuilder documentBuilder = DocumentBuilderFactory.newDefaultInstance() + .newDocumentBuilder(); + var stream = new ByteArrayInputStream(toDeserialize.toString().getBytes()); + Document parsed = documentBuilder.parse(stream); + Forest forest = new Forest(); + forest.createObjectFromXml(parsed); + return forest; + } +} diff --git a/slob/src/main/java/com/iluwatar/slob/serializers/LobSerializer.java b/slob/src/main/java/com/iluwatar/slob/serializers/LobSerializer.java new file mode 100644 index 000000000..54e9c8ba5 --- /dev/null +++ b/slob/src/main/java/com/iluwatar/slob/serializers/LobSerializer.java @@ -0,0 +1,115 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.slob.serializers; + +import com.iluwatar.slob.dbservice.DatabaseService; +import com.iluwatar.slob.lob.Forest; +import java.io.Closeable; +import java.io.IOException; +import java.io.Serializable; +import java.sql.SQLException; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; +import org.xml.sax.SAXException; + +/** + * A LobSerializer can be used to create an instance of a serializer which can serialize and + * deserialize an object and persist and load that object into a DB. from their Binary + * Representation. + */ +public abstract class LobSerializer implements Serializable, Closeable { + + private final transient DatabaseService databaseService; + + /** + * Constructor initializes {@link LobSerializer#databaseService}. + * + * @param dataTypeDb Input provides type of Data to be stored by the Data Base Service + * @throws SQLException If any issue occurs during instantiation of DB Service or during startup. + */ + protected LobSerializer(String dataTypeDb) throws SQLException { + databaseService = new DatabaseService(dataTypeDb); + databaseService.startupService(); + } + + /** + * Provides the specification to Serialize the input object. + * + * @param toSerialize Input Object to serialize + * @return Serialized Object + * @throws ParserConfigurationException if any issue occurs during parsing of input object + * @throws TransformerException if any issue occurs during Transformation + * @throws IOException if any issues occur during reading object + */ + public abstract Object serialize(Forest toSerialize) + throws ParserConfigurationException, TransformerException, IOException; + + /** + * Saves the object to DB with the provided ID. + * + * @param id key to be sent to DB service + * @param name Object name to store in DB + * @param object Object to store in DB + * @return ID with which the object is stored in DB + * @throws SQLException if any issue occurs while saving to DB + */ + public int persistToDb(int id, String name, Object object) throws SQLException { + databaseService.insert(id, name, object); + return id; + } + + /** + * Loads the object from db using the ID and column name. + * + * @param id to query the DB + * @param columnName column from which object is to be extracted + * @return Object from DB + * @throws SQLException if any issue occurs while loading from DB + */ + public Object loadFromDb(int id, String columnName) throws SQLException { + return databaseService.select(id, columnName); + } + + /** + * Provides the specification to Deserialize the input object. + * + * @param toDeserialize object to deserialize + * @return Deserialized Object + * @throws ParserConfigurationException If issue occurs during parsing of input object + * @throws IOException if any issues occur during reading object + * @throws SAXException if any issues occur during reading object for XML parsing + */ + public abstract Forest deSerialize(Object toDeserialize) + throws ParserConfigurationException, IOException, SAXException, ClassNotFoundException; + + @Override + public void close() { + try { + databaseService.shutDownService(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } +} diff --git a/slob/src/test/java/com/iluwatar/slob/AppTest.java b/slob/src/test/java/com/iluwatar/slob/AppTest.java new file mode 100644 index 000000000..f8332a128 --- /dev/null +++ b/slob/src/test/java/com/iluwatar/slob/AppTest.java @@ -0,0 +1,143 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.slob; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import com.iluwatar.slob.lob.Animal; +import com.iluwatar.slob.lob.Forest; +import com.iluwatar.slob.lob.Plant; +import com.iluwatar.slob.serializers.BlobSerializer; +import com.iluwatar.slob.serializers.ClobSerializer; +import com.iluwatar.slob.serializers.LobSerializer; +import java.io.IOException; +import java.sql.SQLException; +import java.util.Collections; +import java.util.Set; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.xml.sax.SAXException; + +/** + * SLOB Application test + */ +@Slf4j +class AppTest { + + /** + * Creates a Forest with Animals and Plants along with their respective relationships. + *

The method creates a forest with 2 Plants Grass and Oak of type Herb and tree + * respectively.

+ *

It also creates 3 animals Zebra and Buffalo which eat the plant grass. Lion consumes the + * Zebra and the Buffalo.

+ *

With the above animals and plants and their relationships a forest + * object is created which represents the Object Graph.

+ * + * @return Forest Object + */ + private static Forest createForest() { + Plant grass = new Plant("Grass", "Herb"); + Plant oak = new Plant("Oak", "Tree"); + + Animal zebra = new Animal("Zebra", Set.of(grass), Collections.emptySet()); + Animal buffalo = new Animal("Buffalo", Set.of(grass), Collections.emptySet()); + Animal lion = new Animal("Lion", Collections.emptySet(), Set.of(zebra, buffalo)); + + return new Forest("Amazon", Set.of(lion, buffalo, zebra), Set.of(grass, oak)); + } + + /** + * Tests the {@link App} without passing any argument in the args to test the + * {@link ClobSerializer}. + */ + @Test + void shouldExecuteWithoutExceptionClob() { + assertDoesNotThrow(() -> App.main(new String[]{"CLOB"})); + } + + /** + * Tests the {@link App} without passing any argument in the args to test the + * {@link BlobSerializer}. + */ + @Test + void shouldExecuteWithoutExceptionBlob() { + assertDoesNotThrow(() -> App.main(new String[]{})); + } + + /** + * Tests the serialization of the input object using the {@link ClobSerializer} and persists the + * serialized object to DB, then load the object back from DB and deserializes it using the + * provided {@link ClobSerializer}.

After loading the object back from DB the test matches the + * hash of the input object with the hash of the object that was loaded from DB and deserialized. + */ + @Test + void clobSerializerTest() { + Forest forest = createForest(); + try (LobSerializer serializer = new ClobSerializer()) { + + Object serialized = serializer.serialize(forest); + int id = serializer.persistToDb(1, forest.getName(), serialized); + + Object fromDb = serializer.loadFromDb(id, Forest.class.getSimpleName()); + Forest forestFromDb = serializer.deSerialize(fromDb); + + Assertions.assertEquals(forest.hashCode(), forestFromDb.hashCode(), + "Hashes of objects after Serializing and Deserializing are the same"); + } catch (SQLException | IOException | TransformerException | ParserConfigurationException | + SAXException | + ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + /** + * Tests the serialization of the input object using the {@link BlobSerializer} and persists the + * serialized object to DB, then loads the object back from DB and deserializes it using the + * {@link BlobSerializer}.

After loading the object back from DB the test matches the hash of + * the input object with the hash of the object that was loaded from DB and deserialized. + */ + @Test + void blobSerializerTest() { + Forest forest = createForest(); + try (LobSerializer serializer = new BlobSerializer()) { + + Object serialized = serializer.serialize(forest); + int id = serializer.persistToDb(1, forest.getName(), serialized); + + Object fromDb = serializer.loadFromDb(id, Forest.class.getSimpleName()); + Forest forestFromDb = serializer.deSerialize(fromDb); + + Assertions.assertEquals(forest.hashCode(), forestFromDb.hashCode(), + "Hashes of objects after Serializing and Deserializing are the same"); + } catch (SQLException | IOException | TransformerException | ParserConfigurationException | + SAXException | + ClassNotFoundException e) { + throw new RuntimeException(e); + } + } +}