diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..5236e1e4621a7a79cad1c2b069faaa994d1bf73b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*~ + diff --git a/Sda1/P/catalog2sql/.gitignore b/Sda1/P/catalog2sql/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..a43ddc84a349317d4788898255b3d8ad744630d4 --- /dev/null +++ b/Sda1/P/catalog2sql/.gitignore @@ -0,0 +1,6 @@ +.project +.classpath +/.settings/ +/target/ +products.sql +A1.log \ No newline at end of file diff --git a/Sda1/P/catalog2sql/Schema/catalog.xsd b/Sda1/P/catalog2sql/Schema/catalog.xsd new file mode 100644 index 0000000000000000000000000000000000000000..959f9f663e401b7ee6e1eeb04d4b1f8a2f2840ca --- /dev/null +++ b/Sda1/P/catalog2sql/Schema/catalog.xsd @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified"> + + <xs:element name="catalog"> + <xs:complexType> + <xs:sequence> + <xs:element ref="product" minOccurs="0" maxOccurs="unbounded"/> + </xs:sequence> + </xs:complexType> + </xs:element> + + <xs:element name="product"> + <xs:complexType> + <xs:sequence> + <xs:element name="name" type="xs:string"/> + <xs:element name="description" type="xs:string" minOccurs="0" maxOccurs="unbounded"/> + <xs:element name="age" type="xs:int" minOccurs="0" maxOccurs="1"/> + </xs:sequence> + <xs:attribute name="id" type="xs:ID" use="required"/> + </xs:complexType> + </xs:element> + +</xs:schema> diff --git a/Sda1/P/catalog2sql/Schema/schema.sql b/Sda1/P/catalog2sql/Schema/schema.sql new file mode 100644 index 0000000000000000000000000000000000000000..c526fef8754be6d183ce7213995ed1415c7d8bd4 --- /dev/null +++ b/Sda1/P/catalog2sql/Schema/schema.sql @@ -0,0 +1,27 @@ +DROP TABLE IF EXISTS Description; +DROP TABLE IF EXISTS Product; + +CREATE TABLE Product ( + id CHAR(20) NOT NULL PRIMARY KEY + ,name VARCHAR(255) NOT NULL + ,age SMALLINT +); + +CREATE TABLE Description ( + product CHAR(20) NOT NULL REFERENCES Product + ,orderIndex int NOT NULL -- preserving the order of descriptions belonging to a given product + ,text VARCHAR(255) NOT NULL + ,UNIQUE(product, orderIndex) +); + +-- example data corresponding to products.xml -- + +-- Product lacking age property -- +INSERT INTO Product (id, name) VALUES ('mpt', 'Monkey Picked Tea'); +INSERT INTO Description VALUES('mpt', 0, 'Picked only by specially trained monkeys'); +INSERT INTO Description VALUES('mpt', 1, 'Rare wild Chinese tea'); + +INSERT INTO Product VALUES ('instantTent', '4-Person Instant Tent', 15); +INSERT INTO Description VALUES('instantTent', 0, 'Exclusive WeatherTec system.'); +INSERT INTO Description VALUES('instantTent', 1, '4-person, 1-room tent'); +INSERT INTO Description VALUES('instantTent', 2, 'Pre-attached tent poles'); diff --git a/Sda1/P/catalog2sql/pom.xml b/Sda1/P/catalog2sql/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..6c12d9132b1823aca7d5b5f2f8a55fbf799f187a --- /dev/null +++ b/Sda1/P/catalog2sql/pom.xml @@ -0,0 +1,96 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <groupId>de.hdm-stuttgart.mi.sda1</groupId> + <artifactId>catalog2sql</artifactId> + <version>0.8</version> + <packaging>jar</packaging> + + <name>catalog2sql</name> + + <!--Fixme: Add a sensible project related domain here --> + <url>http://somedomain.org</url> + + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + </properties> + + <dependencies> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <version>4.11</version> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-api</artifactId> + <version>2.1</version> + </dependency> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-core</artifactId> + <version>2.1</version> + </dependency> + + <dependency> + <groupId>commons-io</groupId> + <artifactId>commons-io</artifactId> + <version>2.4</version> + </dependency> + + <dependency> + <groupId>de.hdm-stuttgart.mi.sda1</groupId> + <artifactId>saxerrorhandler</artifactId> + <version>0.8</version> + </dependency> + + </dependencies> + + <build> + <plugins> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>3.1</version> + <configuration> + <source>1.8</source> + <target>1.8</target> + </configuration> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-javadoc-plugin</artifactId> + <version>2.10.1</version> + <configuration/> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + <version>2.3</version> + <configuration> + <transformers> + <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> + <manifestEntries> + <Main-Class>de.hdm_stuttgart.mi.sda1.catalog2sax.App</Main-Class> + </manifestEntries> + </transformer> + </transformers> + </configuration> + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>shade</goal> + </goals> + </execution> + </executions> + </plugin> + + </plugins> + </build> +</project> diff --git a/ws/eclipse/Jdbc/src/main/java/sax/context/products.xml b/Sda1/P/catalog2sql/products.xml similarity index 69% rename from ws/eclipse/Jdbc/src/main/java/sax/context/products.xml rename to Sda1/P/catalog2sql/products.xml index 7bf5b33fc1fa041bd0b71d6e0fd07dfc5c5bab0c..191ea6b4f3194f2bbbcae5582a0a611c2612eb67 100644 --- a/ws/eclipse/Jdbc/src/main/java/sax/context/products.xml +++ b/Sda1/P/catalog2sql/products.xml @@ -1,13 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE catalog [ - <!ELEMENT catalog (product*)> - <!ELEMENT product (name, description*, age?)> - <!ATTLIST product id ID #REQUIRED> - <!ELEMENT name (#PCDATA)> - <!ELEMENT description (#PCDATA)> - <!ELEMENT age (#PCDATA)> -]> -<catalog> + +<catalog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="Schema/catalog.xsd"> <product id="mpt"> <name>Monkey Picked Tea</name> <description>Rare wild Chinese tea</description> diff --git a/Sda1/P/catalog2sql/src/main/java/de/hdm_stuttgart/mi/sda1/catalog2sql/App.java b/Sda1/P/catalog2sql/src/main/java/de/hdm_stuttgart/mi/sda1/catalog2sql/App.java new file mode 100644 index 0000000000000000000000000000000000000000..4f9e08609d8c8b0b11d74df7b328742ca5ad3afc --- /dev/null +++ b/Sda1/P/catalog2sql/src/main/java/de/hdm_stuttgart/mi/sda1/catalog2sql/App.java @@ -0,0 +1,53 @@ +package de.hdm_stuttgart.mi.sda1.catalog2sql; + +import java.io.IOException; +import java.io.PrintStream; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; + +import de.hdm_stuttgart.mi.sda1.catalog2sql.handler.CatalogContentHandler; +import de.hdm_stuttgart.mi.sda1.saxerrorhandler.handler.SaxErrorHandler; + + +/** + * A simple SAX parser demo + * + */ +public class App { + private static final Logger log = LogManager.getLogger(App.class); + + /** + * @param args Unused + * @throws SAXException + * @throws ParserConfigurationException + * @throws IOException + */ + public static void main( String[] args ) throws ParserConfigurationException, SAXException, IOException { + + log.info("Creating SAX parser"); + final SAXParserFactory saxPf = SAXParserFactory.newInstance(); + final SAXParser saxParser = saxPf.newSAXParser(); + final XMLReader xmlReader = saxParser.getXMLReader(); + + final PrintStream sqlOutputStream = new PrintStream("products.sql"); + final SqlFormatter sqlFormatter = new SqlFormatter(sqlOutputStream); + + log.info("Registering content- and error handler instances"); + xmlReader.setContentHandler(new CatalogContentHandler(sqlFormatter)); + xmlReader.setErrorHandler(new SaxErrorHandler(System.err)); + + + final String xmlDocumentInstanceFilename = "products.xml"; + log.info("Start parsing file '" + xmlDocumentInstanceFilename + "'"); + xmlReader.parse(xmlDocumentInstanceFilename); + + sqlOutputStream.close(); + } +} diff --git a/Sda1/P/catalog2sql/src/main/java/de/hdm_stuttgart/mi/sda1/catalog2sql/SqlFormatter.java b/Sda1/P/catalog2sql/src/main/java/de/hdm_stuttgart/mi/sda1/catalog2sql/SqlFormatter.java new file mode 100644 index 0000000000000000000000000000000000000000..0256793c5caa97a48cb7eafaa56402839a15dac0 --- /dev/null +++ b/Sda1/P/catalog2sql/src/main/java/de/hdm_stuttgart/mi/sda1/catalog2sql/SqlFormatter.java @@ -0,0 +1,61 @@ +package de.hdm_stuttgart.mi.sda1.catalog2sql; + +import java.io.PrintStream; +import java.util.List; + +/** + * Formatting product related INSERT statements + * + */ +public class SqlFormatter { + + final PrintStream output; + + /** + * Writing SQL statements to standard output + */ + public SqlFormatter() { + this.output = System.out; + } + + /** + * @param output SQL output destination stream + */ + public SqlFormatter(final PrintStream output) { + this.output = output; + } + + /** + * Create an SQL INSERT statement corresponding to a <product id='...' > entry + * lacking age specification. + * @param productId See {@link #insertproduct(String, String, int)} + * @param productName See {@link #insertproduct(String, String, int)} + */ + public void insertproduct(final String productId, final String productName) { + output.println("INSERT INTO Product (id, name) VALUES ('" + productId + "', '" + productName + "');"); + } + /** + * Create an SQL INSERT statement corresponding to a <product id='...' > entry + * @param productId The product's unique id property + * @param productName The product's end user readable name. + * @param age The product's age + */ + public void insertproduct(final String productId, final String productName, final int age) { + output.println("INSERT INTO Product VALUES ('" + productId + "', '" + productName + "', " + age + ");"); + } + + + /** + * Adding a description to a given product. + * + * @param productId The description belongs to this product + * @param descriptions All descriptions belonging to productId + */ + public void insertDescription(final String productId, final List<String> descriptions) { + for (int i = 0; i < descriptions.size(); i++) { + output.println("INSERT INTO Description VALUES('" + productId + "', " + i + ", '" + descriptions.get(i) + "');"); + } + output.println("-- end of current product entry --\n"); + } + +} diff --git a/Sda1/P/catalog2sql/src/main/java/de/hdm_stuttgart/mi/sda1/catalog2sql/handler/CatalogContentHandler.java b/Sda1/P/catalog2sql/src/main/java/de/hdm_stuttgart/mi/sda1/catalog2sql/handler/CatalogContentHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..aaf3a08cdf1ed2b2e57462ea9c236e4c49ef5c17 --- /dev/null +++ b/Sda1/P/catalog2sql/src/main/java/de/hdm_stuttgart/mi/sda1/catalog2sql/handler/CatalogContentHandler.java @@ -0,0 +1,126 @@ +package de.hdm_stuttgart.mi.sda1.catalog2sql.handler; + +import java.util.List; +import java.util.Vector; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; + +import de.hdm_stuttgart.mi.sda1.catalog2sql.SqlFormatter; + +/** + * Turning XML data into SQL INSERT statements + * + */ +public class CatalogContentHandler implements ContentHandler { + private static final Logger log = LogManager.getLogger(CatalogContentHandler.class); + Locator locator = null; + + final SqlFormatter sqlFormatter; + + private final List<String> currentDescriptions = new Vector<String>(); + + final StringBuffer currentElementContent = new StringBuffer(); + + String productId, productName, productAgeString; + + /** + * @param sqlFormatter SQL INSERT statement formatter + */ + public CatalogContentHandler(final SqlFormatter sqlFormatter) { + this.sqlFormatter = sqlFormatter; + } + + @Override + public void setDocumentLocator(Locator locator) { + this.locator = locator; + } + // Convenience Method + String displayWithLocatio(final String saxMsg) { + if (null == locator) { + return saxMsg; + } else { + return "File position (" + locator.getLineNumber() + ", " + locator.getColumnNumber() + "): " + saxMsg; + } + } + + @Override + public void startElement(String uri, String localName, String qName, + Attributes attributes) throws SAXException { + currentElementContent.setLength(0); + switch(qName) { + case "product": + productAgeString = null; // Value will be altered if <age> is present + productId = attributes.getValue("id"); + log.info(displayWithLocatio("Product id=" + productId)); + break; + } + } + + @Override + public void endElement(final String uri, final String localName, final String qName) + throws SAXException { + switch(qName) { + case "product": + if (null == productAgeString) { + sqlFormatter.insertproduct(productId, productName); + } else { + try { + sqlFormatter.insertproduct(productId, productName, Integer.parseInt(productAgeString)); + } catch (NumberFormatException ex) { + log.error("Property <age> is not of integer value:" + productAgeString); + } + } + flushDescriptionEntries(); + break; + case "name": + productName = currentElementContent.toString(); + break; + + case "description": + // Do not interfere with the current INSERT INTO Product ... + // statement. Instead postpone related INSERT INTO Description ... + // operations, see flushDescriptionEntries(). + currentDescriptions.add(currentElementContent.toString()); + break; + case "age": + productAgeString = currentElementContent.toString(); + break; + } + } + private void flushDescriptionEntries() { + // Add <description> related INSERTs + sqlFormatter.insertDescription(productId, currentDescriptions); + + // Next <product> may be yet to come, so + // clear the current set of descriptions. + currentDescriptions.clear(); + } + + @Override + public void characters(final char[] ch, final int start, final int length) + throws SAXException { + currentElementContent.append(new String(ch, start,length)); + } + + // We don't need these remaining callbacks + + @Override public void startDocument() throws SAXException {} + @Override public void endDocument() throws SAXException {} + @Override public void startPrefixMapping(String prefix, String uri) + throws SAXException {} + @Override public void endPrefixMapping(String prefix) + throws SAXException {} + @Override public void ignorableWhitespace(char[] ch, int start, int length) + throws SAXException {} + + @Override public void processingInstruction(String target, String data) + throws SAXException {} + + @Override public void skippedEntity(String name) throws SAXException {} + +} \ No newline at end of file diff --git a/Sda1/P/catalog2sql/src/main/resources/log4j2.xml b/Sda1/P/catalog2sql/src/main/resources/log4j2.xml new file mode 100644 index 0000000000000000000000000000000000000000..eda4f3b0d781b09217fb4218285c39adf8d3c9c0 --- /dev/null +++ b/Sda1/P/catalog2sql/src/main/resources/log4j2.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Configuration> + <Appenders> + <File name="A1" fileName="A1.log" append="false"> + <PatternLayout pattern="%t %-5p %c{2} - %m%n"/> + </File> + <Console name="STDOUT" target="SYSTEM_OUT"> + <PatternLayout pattern="%C{2} (%F:%L) - %m%n"/> + </Console> + </Appenders> + <Loggers> + + <!-- You my want to define class or package level per-logger rules --> + <Logger name="de.hdm_stuttgart.mi.sda1.catalog2sax.App" level="debug"> + <AppenderRef ref="A1"/> + </Logger> + <Root level="debug"> + <AppenderRef ref="STDOUT"/> + </Root> + </Loggers> +</Configuration> \ No newline at end of file diff --git a/Sda1/P/catalog2sql/src/test/java/de/hdm_stuttgart/mi/sda1/catalog2sax/AppTest.java b/Sda1/P/catalog2sql/src/test/java/de/hdm_stuttgart/mi/sda1/catalog2sax/AppTest.java new file mode 100644 index 0000000000000000000000000000000000000000..b8da75287a37e3864defd47c2f0894c709c833e7 --- /dev/null +++ b/Sda1/P/catalog2sql/src/test/java/de/hdm_stuttgart/mi/sda1/catalog2sax/AppTest.java @@ -0,0 +1,55 @@ +package de.hdm_stuttgart.mi.sda1.catalog2sax; + +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +import org.apache.commons.io.FileUtils; +import org.junit.Assert; +import org.junit.Test; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; + +import de.hdm_stuttgart.mi.sda1.catalog2sql.SqlFormatter; +import de.hdm_stuttgart.mi.sda1.catalog2sql.handler.CatalogContentHandler; +import de.hdm_stuttgart.mi.sda1.saxerrorhandler.handler.SaxErrorHandler; + +/** + * Unit test comparing the converter's output to a given reference file. + */ +public class AppTest { + /** + * Dummy test method + * @throws SAXException + * @throws IOException + * @throws ParserConfigurationException + */ + @Test + public void testApp() throws IOException, SAXException, ParserConfigurationException { + + final SAXParserFactory saxPf = SAXParserFactory.newInstance(); + final SAXParser saxParser = saxPf.newSAXParser(); + final XMLReader xmlReader = saxParser.getXMLReader(); + + final String sqlGeneratedFilename = "products.sql"; + + final PrintStream sqlOutputStream = new PrintStream(sqlGeneratedFilename); + final SqlFormatter sqlFormatter = new SqlFormatter(sqlOutputStream); + + xmlReader.setContentHandler(new CatalogContentHandler(sqlFormatter)); + xmlReader.setErrorHandler(new SaxErrorHandler(System.err)); + + final String xmlDocumentInstanceFilename = "products.xml"; + xmlReader.parse(xmlDocumentInstanceFilename); + + sqlOutputStream.close(); + final String referenceFilename = "src/test/products.reference.sql"; + + Assert.assertTrue( "Comparing '" + referenceFilename + "' and '" + sqlGeneratedFilename + "'", + FileUtils.contentEquals(new File(referenceFilename), new File(sqlGeneratedFilename))); + } +} diff --git a/Sda1/P/catalog2sql/src/test/products.reference.sql b/Sda1/P/catalog2sql/src/test/products.reference.sql new file mode 100644 index 0000000000000000000000000000000000000000..ec402f32949da9e24517a02b5def3dfd9f75b52e --- /dev/null +++ b/Sda1/P/catalog2sql/src/test/products.reference.sql @@ -0,0 +1,11 @@ +INSERT INTO Product (id, name) VALUES ('mpt', 'Monkey Picked Tea'); +INSERT INTO Description VALUES('mpt', 0, 'Rare wild Chinese tea'); +INSERT INTO Description VALUES('mpt', 1, 'Picked only by specially trained monkeys'); +-- end of current product entry -- + +INSERT INTO Product VALUES ('instantTent', '4-Person Instant Tent', 15); +INSERT INTO Description VALUES('instantTent', 0, '4-person, 1-room tent'); +INSERT INTO Description VALUES('instantTent', 1, 'Pre-attached tent poles'); +INSERT INTO Description VALUES('instantTent', 2, 'Exclusive WeatherTec system.'); +-- end of current product entry -- + diff --git a/Sda1/P/saxerrorhandler/.gitignore b/Sda1/P/saxerrorhandler/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..b21988c2ef29598821d61ae8c314b6cd19d8707c --- /dev/null +++ b/Sda1/P/saxerrorhandler/.gitignore @@ -0,0 +1,6 @@ +/target/ +/.settings/ +.classpath +.project +A1.log +dependency-reduced-pom.xml diff --git a/Sda1/P/saxerrorhandler/pom.xml b/Sda1/P/saxerrorhandler/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..765efc6deafd211678c430014285d6168b8f0487 --- /dev/null +++ b/Sda1/P/saxerrorhandler/pom.xml @@ -0,0 +1,83 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <groupId>de.hdm-stuttgart.mi.sda1</groupId> + <artifactId>saxerrorhandler</artifactId> + <version>0.8</version> + <packaging>jar</packaging> + + <name>saxerrorhandler</name> + + <url>http://www.mi.hdm-stuttgart.de/freedocs</url> + + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + </properties> + + <dependencies> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <version>4.11</version> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-api</artifactId> + <version>2.1</version> + </dependency> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-core</artifactId> + <version>2.1</version> + </dependency> + + </dependencies> + + <build> + <plugins> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>3.1</version> + <configuration> + <source>1.8</source> + <target>1.8</target> + </configuration> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-javadoc-plugin</artifactId> + <version>2.10.1</version> + <configuration/> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + <version>2.3</version> + <configuration> + <transformers> + <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> + <manifestEntries> + <Main-Class>de.hdm_stuttgart.mi.sda1.saxerrorhandler.App</Main-Class> + </manifestEntries> + </transformer> + </transformers> + </configuration> + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>shade</goal> + </goals> + </execution> + </executions> + </plugin> + + </plugins> + </build> +</project> diff --git a/Sda1/P/saxerrorhandler/src/main/java/de/hdm_stuttgart/mi/sda1/saxerrorhandler/handler/SaxErrorHandler.java b/Sda1/P/saxerrorhandler/src/main/java/de/hdm_stuttgart/mi/sda1/saxerrorhandler/handler/SaxErrorHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..169ea841bcb6a659328e508f48ac3e3aeb4c683e --- /dev/null +++ b/Sda1/P/saxerrorhandler/src/main/java/de/hdm_stuttgart/mi/sda1/saxerrorhandler/handler/SaxErrorHandler.java @@ -0,0 +1,69 @@ +package de.hdm_stuttgart.mi.sda1.saxerrorhandler.handler; + +import java.io.PrintStream; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.xml.sax.ErrorHandler; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +/** + * A SAX error handler providing line and column number information with respect + * to files being parsed. + * + * Output will be written to a stream + * + */ +public class SaxErrorHandler implements ErrorHandler { + + private static final Logger log = LogManager.getLogger(SaxErrorHandler.class); + + final PrintStream problemOutputStream; + + /** + * Parsing problems will only published to internal logger. + */ + public SaxErrorHandler() { + problemOutputStream = null; + } + + /** + * Parsing problems will be published to internal logger and + * problemOutputStream. + * + * @param problemOutputStream + */ + public SaxErrorHandler(final PrintStream problemOutputStream) { + this.problemOutputStream = problemOutputStream; + } + + public void warning(SAXParseException e) { + final String msg = "[Warning]" + getLocationString(e); + log.warn(msg); + if (null != problemOutputStream) { + problemOutputStream.print(msg); + } + } + + public void error(SAXParseException e) { + final String msg = "[Error]" + getLocationString(e); + log.error(msg); + if (null != problemOutputStream) { + problemOutputStream.print(msg); + } + } + + public void fatalError(SAXParseException e) throws SAXException { + final String msg = "[Fatal Error]" + getLocationString(e); + log.fatal(msg); + if (null != problemOutputStream) { + problemOutputStream.print(msg); + } + } + + private String getLocationString(SAXParseException e) { + return " line " + e.getLineNumber() + ", column " + e.getColumnNumber() + + ":" + e.getMessage(); + } +} \ No newline at end of file diff --git a/Sda1/P/saxerrorhandler/src/main/resources/log4j2.xml b/Sda1/P/saxerrorhandler/src/main/resources/log4j2.xml new file mode 100644 index 0000000000000000000000000000000000000000..957c8e6943ee8ef125d38c84391fc0746e681900 --- /dev/null +++ b/Sda1/P/saxerrorhandler/src/main/resources/log4j2.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Configuration> + <Appenders> + <File name="A1" fileName="A1.log" append="false"> + <PatternLayout pattern="%t %-5p %c{2} - %m%n"/> + </File> + <Console name="STDOUT" target="SYSTEM_OUT"> + <PatternLayout pattern="%C{2} (%F:%L) - %m%n"/> + </Console> + </Appenders> + <Loggers> + + <!-- You my want to define class or package level per-logger rules --> + <Logger name="de.hdm_stuttgart.mi.sda1.saxerrorhandler.App" level="debug"> + <AppenderRef ref="A1"/> + </Logger> + <Root level="debug"> + <AppenderRef ref="STDOUT"/> + </Root> + </Loggers> +</Configuration> \ No newline at end of file diff --git a/Sda1/P/saxerrorhandler/src/test/java/de/hdm_stuttgart/mi/sda1/saxerrorhandler/AppTest.java b/Sda1/P/saxerrorhandler/src/test/java/de/hdm_stuttgart/mi/sda1/saxerrorhandler/AppTest.java new file mode 100644 index 0000000000000000000000000000000000000000..baccb8de0a0249d81255b23e34d69fcf3e9e3772 --- /dev/null +++ b/Sda1/P/saxerrorhandler/src/test/java/de/hdm_stuttgart/mi/sda1/saxerrorhandler/AppTest.java @@ -0,0 +1,70 @@ +package de.hdm_stuttgart.mi.sda1.saxerrorhandler; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +import org.junit.Assert; +import org.junit.Test; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.DefaultHandler; + +import de.hdm_stuttgart.mi.sda1.saxerrorhandler.handler.SaxErrorHandler; + +/** + * Testing {@link SaxErrorHandler} + */ +public class AppTest { + final static String xmlDocumentInstanceFilename = "src/test/testdata.xml"; + + final XMLReader xmlReader; + + final ByteArrayOutputStream baOs = new ByteArrayOutputStream(); + final PrintStream ps = new PrintStream(baOs); + + /** + * Parser instantiation + * + * @throws SAXException + * @throws ParserConfigurationException + * @throws IOException + */ + public AppTest() throws SAXException, ParserConfigurationException, IOException { + final SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); + + final SAXParser saxParser = saxParserFactory.newSAXParser(); + xmlReader = saxParser.getXMLReader(); + + xmlReader.setFeature("http://xml.org/sax/features/namespaces", true); + xmlReader.setFeature("http://xml.org/sax/features/namespace-prefixes", true); + + xmlReader.setContentHandler(new DefaultHandler()); + xmlReader.setErrorHandler(new SaxErrorHandler(ps)); + } + + /** + * The XML input is not well-formed and should thus yield a fatal error + * thereby throwing a {@link SAXParseException}. + * @throws IOException not expected to be thrown in this test case. + * @throws SAXException + */ + @Test(expected=SAXException.class) + public void testApp() throws IOException, SAXException { + try { + xmlReader.parse(xmlDocumentInstanceFilename); + } catch (final SAXException e) { + System.out.println("baos=" + baOs.toString()); + + final String expectedParsingErrorMessage= + "[Fatal Error] line 5, column 5:The end-tag for element type \"content:person\" must end with a '>' delimiter."; + Assert.assertEquals(expectedParsingErrorMessage, baOs.toString()); + throw e; + } + } +} diff --git a/Sda1/P/saxerrorhandler/src/test/testdata.xml b/Sda1/P/saxerrorhandler/src/test/testdata.xml new file mode 100644 index 0000000000000000000000000000000000000000..acd5445ad0a85078831d83b4930a50f7aefcb5e1 --- /dev/null +++ b/Sda1/P/saxerrorhandler/src/test/testdata.xml @@ -0,0 +1,6 @@ +<root:top xmlns:root="www.mi.hdm-stuttgart.de/sda1.root" + xmlns:content="www.mi.hdm-stuttgart.de/sda1.content"> + + <content:person>Eve</content:person + <content:person>Adam</content:person> +</root:top> \ No newline at end of file diff --git a/Sda1/sda1.xml b/Sda1/sda1.xml index b1b0ae56daac2aae72af9ce526e610a9ab4dd23d..7b045a4acd68e9df6a1c8b8b698bd1cf69fcde57 100644 --- a/Sda1/sda1.xml +++ b/Sda1/sda1.xml @@ -6743,9 +6743,15 @@ localName='HTML'</programlisting> </listitem> <listitem> - <para>description elements are being composed in - <product> elements and should thus be modeled - by a 1:n relation.</para> + <para><tag class="starttag">description</tag> + elements are children of <product> elements + and should thus be modeled by a 1:n relation.</para> + </listitem> + + <listitem> + <para>In a catalog the order of descriptions of a + given product matters. Thus your schema should allow + for descriptions being ordered.</para> </listitem> </itemizedlist> </glossdef> @@ -6775,32 +6781,112 @@ localName='HTML'</programlisting> application:</para> <programlisting language="none"><emphasis role="bold">INSERT INTO Product VALUES ('mpt', 'Monkey picked tea', NULL);</emphasis> -INSERT INTO Description VALUES('mpt', 'Picked only by specially trained monkeys'); -INSERT INTO Description VALUES('mpt', 'Rare wild Chinese tea'); +INSERT INTO Description VALUES('mpt', 0, 'Picked only by specially trained monkeys'); +INSERT INTO Description VALUES('mpt', 1, 'Rare wild Chinese tea'); <emphasis role="bold">INSERT INTO Product VALUES ('instantTent', '4-person instant tent', 15);</emphasis> -INSERT INTO Description VALUES('instantTent', 'Exclusive WeatherTec system.'); -INSERT INTO Description VALUES('instantTent', '4-person, 1-room tent'); -INSERT INTO Description VALUES('instantTent', 'Pre-attached tent poles');</programlisting> +INSERT INTO Description VALUES('instantTent', 0, 'Exclusive WeatherTec system.'); +INSERT INTO Description VALUES('instantTent', 1, '4-person, 1-room tent'); +INSERT INTO Description VALUES('instantTent', 2, 'Pre-attached tent poles');</programlisting> + + <para>Provide a suitable <xref linkend="glo_Junit"/> + test.</para> </glossdef> </glossentry> </glosslist> </question> <answer> - <itemizedlist> - <listitem> - <para><classname>sax.context.Catalog2SqlDriver</classname></para> - </listitem> + <annotation role="make"> + <para role="eclipse">P/catalog2sql</para> + </annotation> + + <para>Some remarks are in order here:</para> + <orderedlist> <listitem> - <para><classname>sax.context.Catalog2Sql</classname></para> + <para>The database schema might read:</para> + + <programlisting language="sql">CREATE TABLE Product ( + id CHAR(20) NOT NULL PRIMARY KEY <co linkends="catalog2sqlSchema-1" + xml:id="catalog2sqlSchema-1-co"/> + ,name VARCHAR(255) NOT NULL + ,age SMALLINT <co linkends="catalog2sqlSchema-2" + xml:id="catalog2sqlSchema-2-co"/> +); + +CREATE TABLE Description ( + product CHAR(20) NOT NULL REFERENCES Product <co + linkends="catalog2sqlSchema-3" + xml:id="catalog2sqlSchema-3-co"/> + ,orderIndex int NOT NULL <co linkends="catalog2sqlSchema-4" + xml:id="catalog2sqlSchema-4-co"/> -- preserving the order of descriptions belonging to a given product + ,text VARCHAR(255) NOT NULL + ,UNIQUE(product, orderIndex) <co linkends="catalog2sqlSchema-5" + xml:id="catalog2sqlSchema-5-co"/> +);</programlisting> + + <calloutlist> + <callout arearefs="catalog2sqlSchema-1-co" + xml:id="catalog2sqlSchema-1"> + <para>The primary key constraint implements the + uniqueness of <tag class="starttag">product + id='xyz'</tag> values</para> + </callout> + + <callout arearefs="catalog2sqlSchema-2-co" + xml:id="catalog2sqlSchema-2"> + <para>Nullability of <code>age</code> implements <tag + class="starttag">age</tag> elements being + optional.</para> + </callout> + + <callout arearefs="catalog2sqlSchema-3-co" + xml:id="catalog2sqlSchema-3"> + <para><tag class="starttag">description</tag> elements + being children of <tag class="starttag">product</tag> + are being implemented by a foreign key to its + identifying owner thus forming weak entities.</para> + </callout> + + <callout arearefs="catalog2sqlSchema-4-co" + xml:id="catalog2sqlSchema-4"> + <para>The attribute <code>orderIndex</code> allows + descriptions to be sorted thus maintaining the + original order of appearance of <tag + class="starttag">description</tag> elements.</para> + </callout> + + <callout arearefs="catalog2sqlSchema-5-co" + xml:id="catalog2sqlSchema-5"> + <para>The <code>orderIndex</code> attribute is unique + within the set of descriptions belonging to the same + product.</para> + </callout> + </calloutlist> </listitem> <listitem> - <para><classname>sax.context.ImportHandler</classname></para> + <para>The result of the given input XML sample file should + read like the content of + <filename>products.reference.xml</filename>:</para> + + <programlisting language="sql">INSERT INTO Product (id, name) VALUES ('mpt', 'Monkey Picked Tea'); +INSERT INTO Description VALUES('mpt', 0, 'Rare wild Chinese tea'); +INSERT INTO Description VALUES('mpt', 1, 'Picked only by specially trained monkeys'); +-- end of current product entry -- + +INSERT INTO Product VALUES ('instantTent', '4-Person Instant Tent', 15); +INSERT INTO Description VALUES('instantTent', 0, '4-person, 1-room tent'); +INSERT INTO Description VALUES('instantTent', 1, 'Pre-attached tent poles'); +INSERT INTO Description VALUES('instantTent', 2, 'Exclusive WeatherTec system.'); +-- end of current product entry --</programlisting> + + <para>So a <xref linkend="glo_Junit"/> test may just + execute the XML to SQL converter and then compare the + effective output to the above reference file.</para> </listitem> - </itemizedlist> + </orderedlist> </answer> </qandaentry> </qandadiv> diff --git a/glossary.xml b/glossary.xml index 7bcaaa3c0e51f64540fa76a0e78a1a9f835fb16c..117ea0f813ba9e200ba2edd4bbc35b7d48b9caeb 100644 --- a/glossary.xml +++ b/glossary.xml @@ -232,6 +232,14 @@ </glossdef> </glossentry> + <glossentry xml:id="glo_Junit"> + <glossterm><acronym>JPA</acronym></glossterm> + + <glossdef> + <para><link xlink:href="http://junit.org">Junit</link></para> + </glossdef> + </glossentry> + <glossentry xml:id="glo_JRE"> <glossterm><trademark>JRE</trademark></glossterm> diff --git a/ws/eclipse/Jdbc/src/main/java/sax/context/Catalog2Sql.java b/ws/eclipse/Jdbc/src/main/java/sax/context/Catalog2Sql.java deleted file mode 100644 index 9e6bb174a7ea9821fce3ac286fce8155671bef03..0000000000000000000000000000000000000000 --- a/ws/eclipse/Jdbc/src/main/java/sax/context/Catalog2Sql.java +++ /dev/null @@ -1,42 +0,0 @@ -package sax.context; - -import java.io.IOException; - -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; - -import org.xml.sax.SAXException; -import org.xml.sax.XMLReader; - -import dom.MySaxErrorHandler; - -/** Parsing catalog instances for import to RDBMS. */ -public class Catalog2Sql { - - /** Set up a validating parser instance. - * - * @throws SAXException Parsing may fail. - * @throws ParserConfigurationException Unable to instantiate parser. - */ - public Catalog2Sql() - - throws SAXException, ParserConfigurationException { - final SAXParserFactory saxPf = SAXParserFactory.newInstance(); - final SAXParser saxParser = saxPf.newSAXParser(); - xmlReader = saxParser.getXMLReader(); - xmlReader.setFeature("http://xml.org/sax/features/validation", true); - xmlReader.setContentHandler(new ImportHandler()); - xmlReader.setErrorHandler(new MySaxErrorHandler(System.out)); - } - /** Initiate parsing - * @param uri The resource to be parsed - * @throws IOException file access problem - * @throws SAXException Parsing may fail. - */ - public void parse(final String uri) - throws IOException, SAXException{ - xmlReader.parse(uri); - } - private final XMLReader xmlReader; -} \ No newline at end of file diff --git a/ws/eclipse/Jdbc/src/main/java/sax/context/Catalog2SqlDriver.java b/ws/eclipse/Jdbc/src/main/java/sax/context/Catalog2SqlDriver.java deleted file mode 100644 index e751827f4321356f28f0d7579f63d69e1d656280..0000000000000000000000000000000000000000 --- a/ws/eclipse/Jdbc/src/main/java/sax/context/Catalog2SqlDriver.java +++ /dev/null @@ -1,10 +0,0 @@ -package sax.context; - -public class Catalog2SqlDriver { - - public static void main(String argv[]) throws Exception{ - final Catalog2Sql importer = new Catalog2Sql(); - importer.parse("src/main/java/sax/context/products.xml"); - } - -} diff --git a/ws/eclipse/Jdbc/src/main/java/sax/context/ImportHandler.java b/ws/eclipse/Jdbc/src/main/java/sax/context/ImportHandler.java deleted file mode 100644 index e79148fe360bb198484b3c75375dec373af23244..0000000000000000000000000000000000000000 --- a/ws/eclipse/Jdbc/src/main/java/sax/context/ImportHandler.java +++ /dev/null @@ -1,95 +0,0 @@ -package sax.context; - -import java.util.HashSet; -import java.util.Set; - -import org.xml.sax.Attributes; -import org.xml.sax.ContentHandler; -import org.xml.sax.Locator; -import org.xml.sax.SAXException; - -/** Reading attributes from element events */ -public class ImportHandler implements ContentHandler { - - private Set<String> currentDescriptions = new HashSet<String>(); - String lastCharacterUnit = null - ,lastAgeString = null - ,currentProductId; - @Override - public void startElement(String uri, String localName, String qName, - Attributes attributes) throws SAXException { - switch(qName) { - case "product": - currentProductId = attributes.getValue("id"); - System.out.print("INSERT INTO Product VALUES ('" + currentProductId + "'"); - } - } - - @Override - public void endElement(final String uri, final String localName, final String qName) - throws SAXException { - switch(qName) { - case "product": - if (null == lastAgeString) { - System.out.print( ", NULL"); - } else { - try { - System.out.print(", " + Integer.parseInt(lastCharacterUnit)); - } catch (NumberFormatException ex) { - System.err.println("Age is not an integer value:" + lastCharacterUnit); - } - lastAgeString = null; - } - - System.out.println(");"); - flushDescriptionEntries(); - break; - case "name": - System.out.print(", '" + lastCharacterUnit + "'"); - break; - - case "description": - // Do not interfere with the current INSERT INTO Product ... - // statement. Instead postpone related INSERT INTO Description ... - // operations, see flushDescriptionEntries(). - currentDescriptions.add(lastCharacterUnit); - break; - case "age": - lastAgeString = lastCharacterUnit; - break; - } - } - private void flushDescriptionEntries() { - // Add <description> related INSERTs - for (final String description: currentDescriptions) { - System.out.println("INSERT INTO Description VALUES('" + - currentProductId + "', '" + description + "');"); - } - // Next <product> is yet to come, so - // clear the current set of descriptions. - currentDescriptions.clear(); - } - - @Override - public void characters(final char[] ch, final int start, final int length) - throws SAXException { - lastCharacterUnit = new String(ch, start,length); - } - - // We don't need these remaining callbacks - - @Override public void setDocumentLocator(Locator locator) {} - @Override public void startDocument() throws SAXException {} - @Override public void endDocument() throws SAXException {} - @Override public void startPrefixMapping(String prefix, String uri) - throws SAXException {} - @Override public void endPrefixMapping(String prefix) - throws SAXException {} - @Override public void ignorableWhitespace(char[] ch, int start, int length) - throws SAXException {} - - @Override public void processingInstruction(String target, String data) - throws SAXException {} - - @Override public void skippedEntity(String name) throws SAXException {} -} \ No newline at end of file diff --git a/ws/eclipse/Jdbc/src/main/java/sax/context/schema.sql b/ws/eclipse/Jdbc/src/main/java/sax/context/schema.sql deleted file mode 100644 index 60f6bcccef84d04152ceff0755fa07c8b20b8791..0000000000000000000000000000000000000000 --- a/ws/eclipse/Jdbc/src/main/java/sax/context/schema.sql +++ /dev/null @@ -1,23 +0,0 @@ -CREATE TABLE Product ( - id CHAR(20) NOT NULL PRIMARY KEY - ,name VARCHAR(255) NOT NULL - ,age SMALLINT -); - -CREATE TABLE Description ( - product CHAR(20) NOT NULL REFERENCES Product - ,text VARCHAR(255) NOT NULL -); - --- example data corresponding to products.xml -- -INSERT INTO Product VALUES ('mpt', 'Monkey Picked Tea', NULL); -INSERT INTO Description VALUES('mpt', 'Picked only by specially trained monkeys'); -INSERT INTO Description VALUES('mpt', 'Rare wild Chinese tea'); -INSERT INTO Product VALUES ('instantTent', '4-Person Instant Tent', 15); -INSERT INTO Description VALUES('instantTent', 'Exclusive WeatherTec system.'); -INSERT INTO Description VALUES('instantTent', '4-person, 1-room tent'); -INSERT INTO Description VALUES('instantTent', 'Pre-attached tent poles'); - --- tidy up -- -DROP TABLE Description; -DROP TABLE Product; \ No newline at end of file