package me.adaptive.arp.common.parser.xml;/*
* =| ADAPTIVE RUNTIME PLATFORM |=======================================================================================
*
* (C) Copyright 2013-2014 Carlos Lozano Diez t/a Adaptive.me <http://adaptive.me>.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* Original author:
*
* * Carlos Lozano Diez
* <http://github.com/carloslozano>
* <http://twitter.com/adaptivecoder>
* <mailto:carlos@adaptive.me>
*
* Contributors:
*
* * Francisco Javier Martin Bueno
* <https://github.com/kechis>
* <mailto:kechis@gmail.com>
*
* =====================================================================================================================
*/
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import me.adaptive.arp.api.AppRegistryBridge;
import me.adaptive.arp.api.ILogging;
import me.adaptive.arp.api.ILoggingLogLevel;
import me.adaptive.arp.api.IServiceMethod;
import me.adaptive.arp.api.IServiceType;
import me.adaptive.arp.api.Locale;
import me.adaptive.arp.api.Service;
import me.adaptive.arp.api.ServiceEndpoint;
import me.adaptive.arp.api.ServicePath;
import me.adaptive.arp.api.ServiceToken;
import me.adaptive.arp.common.core.AppResourceManager;
import me.adaptive.arp.common.parser.plist.PList;
import me.adaptive.arp.common.parser.plist.PListParser;
public class XmlParser {
private static String COUNTRY_ATT = "country";
private static String LANGUAGE_ATT = "language";
private static String SERVICE_TAG = "service";
private static final String NAME_ATT = "name";
private static final String ENDPOINT_TAG = "end-point";
private static final String HOST_ATT = "host";
private static final String validation_ATT = "validation";
private static final String PATH_TAG = "path";
private static final String PATH_ATT = "path";
private static final String TYPE_ATT = "type";
private static final String METHOD_TAG = "method";
private static final String METHOD_ATT = "method";
private static String RESOURCE_TAG = "resource";
private static String URL_ATT = "url";
private static final String APP_DEFINITIONS_CONFIG_PATH = "definitions/";
private static final String IO_CONFIG_FILENAME = "io-config.xml";
private static final String IO_CONFIG_DEFINITION_FILENAME = APP_DEFINITIONS_CONFIG_PATH + "i18n-config.xsd";
private static final String I18N_CONFIG_FILENAME = "i18n-config.xml";
private static final String I18N_DEFINITIONS_CONFIG_FILENAME = APP_DEFINITIONS_CONFIG_PATH + "i18n-config.xsd";
private static final String PLIST_EXTENSION = ".plist";
private static final String DEFAULT_LOCALE_TAG = "default";
private static final String SUPPORTED_LOCALE_TAG = "supportedLanguage";
// logger
private static final String LOG_TAG = "DeviceDelegate";
private static ILogging logger = AppRegistryBridge.getInstance().getLoggingBridge();
private List<Locale> locales;
private static XmlParser instance = null;
protected XmlParser() {
// Exists only to defeat instantiation.
initialize();
}
public static XmlParser getInstance() {
if (instance == null) {
instance = new XmlParser();
}
return instance;
}
private List<String> resources = null;
private Map<String, Service> services = null;
private Locale defaultLocale;
private List<Locale> supportedLocale = null;
private Map<String, PList> i18nData = null;
public List<Locale> getLocales() {
return locales;
}
public Locale getDefaultLocale() {
return defaultLocale;
}
public List<Locale> getSupportedLocale() {
return supportedLocale;
}
public Map<String, PList> getI18nData() {
return i18nData;
}
public List<String> getResources() {
return resources;
}
public Map<String, Service> getServices() {
return services;
}
/**
* Init function -- should be called just once
*/
private void initialize() {
services = new HashMap<>();
resources = new ArrayList<>();
supportedLocale = new ArrayList<>();
i18nData = new HashMap<String, PList>();
InputStream plistIS = null, origin = null, validator = null;
try {
origin = new ByteArrayInputStream(AppResourceManager.getInstance().retrieveConfigResource(IO_CONFIG_FILENAME).getData());
validator = new ByteArrayInputStream(AppResourceManager.getInstance().retrieveConfigResource(IO_CONFIG_DEFINITION_FILENAME).getData());
// if (validate(origin, "io")) {
// logger.log(ILoggingLogLevel.Debug, LOG_TAG, "VALID");
// } else logger.log(ILoggingLogLevel.Error, LOG_TAG, "INVALID");
Document document = this.parseXml(origin, validator);
resources = this.getResourceData(document);
services = this.getIOData(document);
origin = new ByteArrayInputStream(AppResourceManager.getInstance().retrieveConfigResource(I18N_CONFIG_FILENAME).getData());
validator = new ByteArrayInputStream(AppResourceManager.getInstance().retrieveConfigResource(I18N_DEFINITIONS_CONFIG_FILENAME).getData());
// if (validate(origin, "i18n")) {
// logger.log(ILoggingLogLevel.Debug, LOG_TAG, "VALID");
// } else logger.log(ILoggingLogLevel.Error, LOG_TAG, "INVALID");
document = this.parseXml(origin, validator);
defaultLocale = this.getLocaleData(document, DEFAULT_LOCALE_TAG).get(0);
supportedLocale = this.getLocaleData(document, SUPPORTED_LOCALE_TAG);
for (Locale locale : supportedLocale) {
plistIS = new ByteArrayInputStream(AppResourceManager.getInstance().retrieveConfigResource(getResourcesFilePath(locale)).getData());
PList plist = PListParser.getInstance().parse(plistIS);
i18nData.put(localeToString(locale), plist);
}
} catch (IOException e) {
logger.log(ILoggingLogLevel.Error, LOG_TAG, "Error Opening xml - Error: " + e.getLocalizedMessage());
} catch (ParserConfigurationException e) {
logger.log(ILoggingLogLevel.Error, LOG_TAG, "Error Parsing xml - Error: " + e.getLocalizedMessage());
} catch (SAXException e) {
logger.log(ILoggingLogLevel.Error, LOG_TAG, "Error Validating xml - Error: " + e.getLocalizedMessage());
} catch (Exception e) {
logger.log(ILoggingLogLevel.Error, LOG_TAG, "Error Validating xml - Error: " + e.getLocalizedMessage());
} finally {
closeStream(plistIS);
closeStream(origin);
closeStream(validator);
}
}
/**
* Return the Document parsed from InputStream
*
* @param xml origin InputStream
* @param xsd validation InputStream
* @return Document parsed
* @throws IOException
* @throws ParserConfigurationException
* @throws SAXException
*/
public Document parseXml(InputStream xml, InputStream xsd) throws IOException, ParserConfigurationException, SAXException {
// parse an XML document into a DOM tree
DocumentBuilderFactory parserFactory = DocumentBuilderFactory.newInstance();
parserFactory.setNamespaceAware(true);
DocumentBuilder parser = parserFactory.newDocumentBuilder();
Document document = parser.parse(xml);
return document;
}
/**
* Returns the i18n locale data
*
* @param document to read
* @param tag node to read
* @return locales data
* @throws ParserConfigurationException
* @throws SAXException
* @throws IOException
*/
public List<Locale> getLocaleData(Document document, String tag) throws ParserConfigurationException, SAXException, IOException {
locales = new ArrayList<>();
Element docEle = document.getDocumentElement();
NodeList nl = docEle.getElementsByTagName(tag);
if (nl != null && nl.getLength() > 0) {
for (int i = 0; i < nl.getLength(); i++) {
//get the employee element
Element el = (Element) nl.item(i);
//get the Employee object
Locale locale = getLocale(el);
locales.add(locale);
}
}
return locales;
}
/**
* Returns a Locale from xml element
*
* @param empEl containing the data
* @return a Locale
*/
private Locale getLocale(Element empEl) {
String country = empEl.getAttribute(COUNTRY_ATT);
String language = empEl.getAttribute(LANGUAGE_ATT);
//Create a new Locale with the value read from the xml nodes
return new Locale(language, country);
}
/**
* Returns the IO Data from a Document
*
* @param document Document
* @return services data
* @throws ParserConfigurationException
* @throws SAXException
* @throws IOException
*/
public Map<String, Service> getIOData(Document document) throws ParserConfigurationException, SAXException, IOException {
//List<Service> services = new ArrayList<>();
Map<String, Service> services = new HashMap<>();
Element docEle = document.getDocumentElement();
NodeList nl = docEle.getElementsByTagName(SERVICE_TAG);
if (nl != null && nl.getLength() > 0) {
for (int i = 0; i < nl.getLength(); i++) {
Element el = (Element) nl.item(i);
Service serv = getService(el);
services.put(serv.getName(), serv);
}
}
return services;
}
/**
* Returns Service from xml element
*
* @param empEl containing the data
* @return Service
*/
private Service getService(Element empEl) {
List<ServiceEndpoint> endpoints = new ArrayList<>();
NodeList nl = empEl.getElementsByTagName(ENDPOINT_TAG);
if (nl != null && nl.getLength() > 0) {
for (int i = 0; i < nl.getLength(); i++) {
Element el = (Element) nl.item(i);
endpoints.add(getEndPoint(el));
}
}
String name = empEl.getAttribute(NAME_ATT);
return new Service(endpoints.toArray(new ServiceEndpoint[endpoints.size()]), name);
}
/**
* Returns and ServiceEndpoint from Xml element
*
* @param el xml element
* @return ServiceEndpoint
*/
private ServiceEndpoint getEndPoint(Element el) {
List<ServicePath> paths = new ArrayList<>();
NodeList nl = el.getElementsByTagName(PATH_TAG);
for (int i = 0; i < nl.getLength(); i++) {
Element ele = (Element) nl.item(i);
paths.add(getPath(ele));
}
return new ServiceEndpoint(el.getAttribute(HOST_ATT), paths.toArray(new ServicePath[paths.size()]));
}
/**
* Return the ServicePath from an Element
*
* @param el Element
* @return ServicePath
*/
private ServicePath getPath(Element el) {
List<IServiceMethod> methods = new ArrayList<>();
NodeList nl = el.getElementsByTagName(METHOD_TAG);
if (nl != null && nl.getLength() > 0) {
Element ele = (Element) nl.item(0);
IServiceMethod method;
switch (ele.getAttribute(METHOD_ATT).toUpperCase(java.util.Locale.ENGLISH)) {
case "GET":
method = IServiceMethod.Get;
break;
case "POST":
method = IServiceMethod.Post;
break;
case "HEAD":
method = IServiceMethod.Head;
break;
default:
method = IServiceMethod.Unknown;
}
methods.add(method);
}
IServiceType type;
switch (el.getAttribute(TYPE_ATT).toUpperCase(java.util.Locale.ENGLISH)) {
case "RESTJSON":
type = IServiceType.RestJson;
break;
case "OCTETBINARY":
type = IServiceType.OctetBinary;
break;
case "RESTXML":
type = IServiceType.RestXml;
break;
case "SOAPXML":
type = IServiceType.SoapXml;
break;
default:
type = IServiceType.Unknown;
}
return new ServicePath(el.getAttribute(PATH_ATT), methods.toArray(new IServiceMethod[methods.size()]), type);
}
/**
* Return all whitelisted resources url
*
* @param document source
* @return Whitelisted urls
* @throws ParserConfigurationException
* @throws SAXException
* @throws IOException
*/
public List<String> getResourceData(Document document) throws ParserConfigurationException, SAXException, IOException {
List<String> resources = new ArrayList<>();
Element docEle = document.getDocumentElement();
NodeList nl = docEle.getElementsByTagName(RESOURCE_TAG);
if (nl != null && nl.getLength() > 0) {
for (int i = 0; i < nl.getLength(); i++) {
Element el = (Element) nl.item(i);
resources.add(getResource(el));
}
}
return resources;
}
/**
* Return a Resource whitelist url from xml element
*
* @param el Xml element
* @return url string
*/
private String getResource(Element el) {
return el.getAttribute(URL_ATT);
}
/**
* Close given InputStream
*
* @param is inputString
*/
private static void closeStream(InputStream is) {
try {
if (is != null) {
is.close();
}
} catch (Exception ex) {
logger.log(ILoggingLogLevel.Error, LOG_TAG, "Error closing stream: " + ex.getLocalizedMessage());
}
}
/**
* get the absolute path for resources
*
* @param locale data
* @return The string with the path
*/
private String getResourcesFilePath(Locale locale) {
return localeToString(locale) + PLIST_EXTENSION;
}
/**
* Return the String representation of the Locale
*
* @param locale object
* @return String
*/
private String localeToString(Locale locale) {
return locale.getLanguage() + "-" + locale.getCountry();
}
/**
* Returns the content type for a ServiceToken
*
* @param serviceToken
* @return IServiceType
*/
public IServiceType getContentType(ServiceToken serviceToken) {
if (services.containsKey(serviceToken.getServiceName())) {
for (ServiceEndpoint serviceEndpoint : services.get(serviceToken.getServiceName()).getServiceEndpoints()) {
for (ServicePath servicePath : serviceEndpoint.getPaths()) {
if (servicePath.getPath().equals(serviceToken.getFunctionName())) {
return servicePath.getType();
}
}
}
}
return null;
}
/* *
* Validation method.
*
* @param xmlFilePath The xml file we are trying to validate.
* @param xmlSchemaFilePath The schema file we are using for the validation. This method assumes the schema file is valid.
* @return True if valid, false if not valid or bad parse or exception/error during parse.
* /
private static boolean validate(InputStream xmlFilePath, InputStream xmlSchemaFilePath) {
//TODO MAKE THE XSD VALIDATION
// Try the validation, we assume that if there are any issues with the validation
// process that the input is invalid.
try {
//SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
SchemaFactory factory = new XMLSchemaFactory();
Source schemaFile = new StreamSource(createFileFromInputStream(xmlSchemaFilePath));
Source xmlSource = new StreamSource(createFileFromInputStream(xmlFilePath));
Schema schema = factory.newSchema(schemaFile);
Validator validator = schema.newValidator();
validator.validate(xmlSource);
} catch (SAXException e) {
return false;
} catch (IOException e) {
return false;
} catch (Exception e) {
// Catches everything beyond: SAXException, and IOException.
logger.log(ILoggingLogLevel.Error, LOG_TAG, "Error closing stream: " + e.getLocalizedMessage());
return false;
} catch (Error e) {
// Needed this for debugging when I was having issues with my 1st set of code.
logger.log(ILoggingLogLevel.Error, LOG_TAG, "Error closing stream: " + e.getLocalizedMessage());
return false;
}
return true;
}*/
/* Create a File from InputStream */
private static File createFileFromInputStream(InputStream inputStream) {
try {
File f = new File(AppRegistryBridge.getInstance().getFileSystemBridge().getApplicationCacheFolder().getPathAbsolute().concat("/" + new Date().getTime() + ".cache"));
OutputStream outputStream = new FileOutputStream(f);
byte buffer[] = new byte[1024];
int length = 0;
while ((length = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, length);
}
outputStream.close();
inputStream.close();
return f;
} catch (IOException e) {
logger.log(ILoggingLogLevel.Error, LOG_TAG, "Error closing stream: " + e.getLocalizedMessage());
}
return null;
}
/* Create a File from a content String */
public static void createFileFromString(String fileText, String fileName) {
try {
File file = new File(fileName);
BufferedWriter output = new BufferedWriter(new FileWriter(file));
output.write(fileText);
output.close();
} catch (IOException e) {
logger.log(ILoggingLogLevel.Error, LOG_TAG, "Error closing stream: " + e.getLocalizedMessage());
}
}
}