diff --git a/pom.xml b/pom.xml index 44178fd1af54..2e1b3312b38b 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 000000000000..13ef6cb63863 --- /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 000000000000..e852dfb21d08 Binary files /dev/null and b/slob/etc/slob.urm.png differ diff --git a/slob/etc/slob.urm.uml b/slob/etc/slob.urm.uml new file mode 100644 index 000000000000..1d9de1e46d3b --- /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 000000000000..5f95d4c1b2c2 --- /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 000000000000..743cc8293f1f --- /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 000000000000..ea54592c99d1 --- /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 000000000000..770543cc5cf3 --- /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 000000000000..1c14d55debd8 --- /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 000000000000..20aff543eacb --- /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 000000000000..c3858a84a9b6 --- /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 000000000000..1734447194d0 --- /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 000000000000..54e9c8ba52ec --- /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 000000000000..f8332a128020 --- /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); + } + } +}