/**
* AnalyzerBeans
* Copyright (C) 2014 Neopost - Customer Information Management
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.eobjects.analyzer.configuration;
import java.io.InputStream;
import java.util.Arrays;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.eobjects.analyzer.connection.CsvDatastore;
import org.eobjects.analyzer.connection.Datastore;
import org.eobjects.analyzer.connection.DatastoreCatalog;
import org.eobjects.analyzer.connection.ExcelDatastore;
import org.eobjects.analyzer.connection.JdbcDatastore;
import org.eobjects.analyzer.util.StringUtils;
import org.apache.metamodel.csv.CsvConfiguration;
import org.apache.metamodel.schema.TableType;
import org.apache.metamodel.util.FileResource;
import org.apache.metamodel.util.Func;
import org.apache.metamodel.util.Resource;
import org.apache.metamodel.xml.XmlDomDataContext;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import com.google.common.base.Strings;
/**
* Utility class for externalizing datastores to the XML format of conf.xml.
*
* Generally speaking, XML elements created by this class, and placed in a the
* <datastore-catalog> element of conf.xml, will be readable by
* {@link JaxbConfigurationReader}.
*/
public class DatastoreXmlExternalizer {
private final Document _document;
public DatastoreXmlExternalizer() {
final DocumentBuilder documentBuilder;
try {
final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilder = documentBuilderFactory.newDocumentBuilder();
} catch (Exception e) {
throw new IllegalStateException(e);
}
_document = documentBuilder.newDocument();
}
public DatastoreXmlExternalizer(Resource resource) {
_document = resource.read(new Func<InputStream, Document>() {
@Override
public Document eval(InputStream in) {
try {
final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
final DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
return documentBuilder.parse(in);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
});
}
public DatastoreXmlExternalizer(Document document) {
_document = document;
}
/**
* Determines if the given datastore is externalizable by this object.
*
* @param datastore
* @return
*/
public boolean isExternalizable(final Datastore datastore) {
if (datastore == null) {
return false;
}
if (datastore instanceof JdbcDatastore) {
return true;
}
if (datastore instanceof CsvDatastore) {
final Resource resource = ((CsvDatastore) datastore).getResource();
if (resource instanceof FileResource) {
return true;
}
}
if (datastore instanceof ExcelDatastore) {
final Resource resource = ((ExcelDatastore) datastore).getResource();
if (resource instanceof FileResource) {
return true;
}
}
return false;
}
/**
* Removes a datastore by it's name, if it exists and is recognizeable by
* the externalizer.
*
* @param datastoreName
* @return true if a datastore element was removed from the XML document.
*/
public boolean removeDatastore(final String datastoreName) {
final Element datastoreCatalogElement = getDatastoreCatalogElement();
final NodeList childNodes = datastoreCatalogElement.getChildNodes();
final int length = childNodes.getLength();
for (int i = 0; i < length; i++) {
final Node node = childNodes.item(i);
if (node instanceof Element) {
final Element element = (Element) node;
final Attr[] attributes = XmlDomDataContext.getAttributes(element);
for (Attr attr : attributes) {
if ("name".equals(attr.getName())) {
final String value = attr.getValue();
if (datastoreName.equals(value)) {
// we have a match
datastoreCatalogElement.removeChild(element);
onDocumentChanged(getDocument());
return true;
}
}
}
}
}
return false;
}
/**
* Externalizes the given datastore
*
* @param datastore
* @return
* @throws UnsupportedOperationException
*/
public Element externalize(Datastore datastore) throws UnsupportedOperationException {
if (datastore == null) {
throw new IllegalArgumentException("Datastore cannot be null");
}
final Element elem;
if (datastore instanceof CsvDatastore) {
final Resource resource = ((CsvDatastore) datastore).getResource();
final String filename = toFilename(resource);
elem = toElement((CsvDatastore) datastore, filename);
} else if (datastore instanceof ExcelDatastore) {
final Resource resource = ((ExcelDatastore) datastore).getResource();
final String filename = toFilename(resource);
elem = toElement((ExcelDatastore) datastore, filename);
} else if (datastore instanceof JdbcDatastore) {
elem = toElement((JdbcDatastore) datastore);
} else {
throw new UnsupportedOperationException("Non-supported datastore: " + datastore);
}
final Element datastoreCatalogElement = getDatastoreCatalogElement();
datastoreCatalogElement.appendChild(elem);
onDocumentChanged(getDocument());
return elem;
}
/**
* Overrideable method, invoked whenever the document has changed
*
* @param document
*/
protected void onDocumentChanged(Document document) {
}
/**
* Creates a filename string to externalize, based on a given
* {@link Resource}.
*
* @param resource
* @return
* @throws UnsupportedOperationException
*/
protected String toFilename(final Resource resource) throws UnsupportedOperationException {
if (resource instanceof FileResource) {
return ((FileResource) resource).getFile().getPath();
}
throw new UnsupportedOperationException("Unsupported resource type: " + resource);
}
/**
* Externalizes a {@link JdbcDatastore} to a XML element.
*
* @param datastore
* @param doc
* @return
*/
public Element toElement(JdbcDatastore datastore) {
final Element ds = getDocument().createElement("jdbc-datastore");
ds.setAttribute("name", datastore.getName());
if (!StringUtils.isNullOrEmpty(datastore.getDescription())) {
ds.setAttribute("description", datastore.getDescription());
}
String jndiUrl = datastore.getDatasourceJndiUrl();
if (Strings.isNullOrEmpty(jndiUrl)) {
appendElement(ds, "url", datastore.getJdbcUrl());
appendElement(ds, "driver", datastore.getDriverClass());
appendElement(ds, "username", datastore.getUsername());
appendElement(ds, "password", datastore.getPassword());
appendElement(ds, "multiple-connections", datastore.isMultipleConnections() + "");
} else {
appendElement(ds, "datasource-jndi-url", jndiUrl);
}
final TableType[] tableTypes = datastore.getTableTypes();
if (tableTypes != null && tableTypes.length != 0 && !Arrays.equals(TableType.DEFAULT_TABLE_TYPES, tableTypes)) {
final Element tableTypesElement = getDocument().createElement("table-types");
ds.appendChild(tableTypesElement);
for (final TableType tableType : tableTypes) {
appendElement(tableTypesElement, "table-type", tableType.name());
}
}
final String catalogName = datastore.getCatalogName();
if (!Strings.isNullOrEmpty(catalogName)) {
appendElement(ds, "catalog-name", catalogName);
}
return ds;
}
/**
* Externalizes a {@link ExcelDatastore} to a XML element.
*
* @param datastore
* @param filename
* the filename/path to use in the XML element. Since the
* appropriate path will depend on the reading application's
* environment (supported {@link Resource} types), this specific
* property of the datastore is provided separately.
* @return
*/
public Element toElement(ExcelDatastore datastore, String filename) {
final Element ds = getDocument().createElement("excel-datastore");
ds.setAttribute("name", datastore.getName());
if (!StringUtils.isNullOrEmpty(datastore.getDescription())) {
ds.setAttribute("description", datastore.getDescription());
}
appendElement(ds, "filename", filename);
return ds;
}
/**
* Externalizes a {@link CsvDatastore} to a XML element.
*
* @param datastore
* the datastore to externalize
* @param filename
* the filename/path to use in the XML element. Since the
* appropriate path will depend on the reading application's
* environment (supported {@link Resource} types), this specific
* property of the datastore is provided separately.
* @return a XML element representing the datastore.
*/
public Element toElement(CsvDatastore datastore, String filename) {
final Element datastoreElement = getDocument().createElement("csv-datastore");
datastoreElement.setAttribute("name", datastore.getName());
final String description = datastore.getDescription();
if (!StringUtils.isNullOrEmpty(description)) {
datastoreElement.setAttribute("description", description);
}
appendElement(datastoreElement, "filename", filename);
appendElement(datastoreElement, "quote-char", datastore.getQuoteChar());
appendElement(datastoreElement, "separator-char", datastore.getSeparatorChar());
appendElement(datastoreElement, "escape-char", datastore.getEscapeChar());
appendElement(datastoreElement, "encoding", datastore.getEncoding());
appendElement(datastoreElement, "fail-on-inconsistencies", datastore.isFailOnInconsistencies());
appendElement(datastoreElement, "multiline-values", datastore.isMultilineValues());
appendElement(datastoreElement, "header-line-number", datastore.getHeaderLineNumber());
return datastoreElement;
}
/**
* Gets the XML document that has been built.
*
* @return
*/
public final Document getDocument() {
return _document;
}
/**
* Gets the XML element that represents the {@link DatastoreCatalog}.
*
* @return
*/
public Element getDatastoreCatalogElement() {
final Element configurationFileDocumentElement = getDocumentElement();
final Element datastoreCatalogElement = getOrCreateChildElementByTagName(configurationFileDocumentElement,
"datastore-catalog");
if (datastoreCatalogElement == null) {
throw new IllegalStateException("Could not find <datastore-catalog> element in configuration file");
}
return datastoreCatalogElement;
}
private Element getDocumentElement() {
final Document document = getDocument();
Element documentElement = document.getDocumentElement();
if (documentElement == null) {
documentElement = document.createElement("configuration");
document.appendChild(documentElement);
}
return documentElement;
}
private Element getOrCreateChildElementByTagName(Element element, String tagName) {
Element elem = getChildElementByTagName(element, tagName);
if (elem == null) {
elem = getDocument().createElement(tagName);
final Element configurationFileDocumentElement = getDocumentElement();
configurationFileDocumentElement.appendChild(elem);
}
return elem;
}
private Element getChildElementByTagName(Element element, String tagName) {
final NodeList nodeList = element.getElementsByTagName(tagName);
if (nodeList == null) {
return null;
}
final int length = nodeList.getLength();
for (int i = 0; i < length; i++) {
final Node node = nodeList.item(i);
if (node instanceof Element) {
return (Element) node;
}
}
return null;
}
private void appendElement(Element parent, String elementName, Object value) {
if (value == null) {
return;
}
String stringValue = value.toString();
if (value instanceof Character) {
final char c = ((Character) value).charValue();
if (c == CsvConfiguration.NOT_A_CHAR) {
stringValue = "NOT_A_CHAR";
} else if (c == '\t') {
stringValue = "\\t";
} else if (c == '\n') {
stringValue = "\\n";
} else if (c == '\r') {
stringValue = "\\r";
}
}
final Element element = getDocument().createElement(elementName);
element.setTextContent(stringValue);
parent.appendChild(element);
}
}