Package ch.inftec.ju.util.xml

Source Code of ch.inftec.ju.util.xml.XmlUtils$MarshallerBuilder$NamespacePrefixMapperAdapter

package ch.inftec.ju.util.xml;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.URL;
import java.util.GregorianCalendar;
import java.util.TimeZone;
import java.util.concurrent.ExecutionException;

import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.xml.sax.InputSource;
import org.xml.sax.SAXParseException;

import ch.inftec.ju.util.AssertUtil;
import ch.inftec.ju.util.IOUtil;
import ch.inftec.ju.util.JuException;
import ch.inftec.ju.util.JuRuntimeException;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

/**
* Utility class containing XML related helper methods.
* @author tgdmemae
*
*/
public class XmlUtils {
  /**
     * Needed to create XMLGregorianCalendar instances
     */
    private static DatatypeFactory df = null;

    /**
     * Gets an instance of a DatatypeFactory. This is needed to create XMLGregorianCalendar instances.
     * @return DatatypeFactory instance
     */
    private static synchronized DatatypeFactory getDatetypeFactory() {
      if (df == null) {
        try {
                df = DatatypeFactory.newInstance();
            } catch (DatatypeConfigurationException dce) {
                throw new IllegalStateException(
                    "Exception while obtaining DatatypeFactory instance", dce);
            }
      }
      return df;
    }
  /**
   * Don't instantiate.
   */
  private XmlUtils() {
    throw new AssertionError("use only statically");
  }
 
  /**
   * Loads and parses an XML into a DOM structure. This method doesn't perform schema
   * validation.
   * @param xmlUrl URL to the XML
   * @return Document instance
   * @throws JuException If the XML cannot be loaded
   */
  public static Document loadXml(URL xmlUrl) throws JuException {
    return XmlUtils.loadXml(xmlUrl, null);
  }
 
  /**
   * Loads the specified XML resource into a DOM structure and wraps it with an XPathGetter.
   * @param xmlUrl URL to the XML
   * @return XPathGetter instance on the document
   * @throws JuException If the XML cannot be loaded
   */
  public static XPathGetter loadXmlAsXPathGetter(URL xmlUrl) throws JuException {
    return new XPathGetter(XmlUtils.loadXml(xmlUrl));
  }
 
  /**
   * Loads and parses an XML into a DOM structure.
   * @param xmlUrl URL to the XML
   * @param schemaUrl URL to an optional XML validation schema. If null, no validation is done.
   * @return Document instance
   * @throws JuException If the XML cannot be loaded
   */
  public static Document loadXml(URL xmlUrl, URL schemaUrl) throws JuException {
    InputStream xmlStream = null;
   
    try {
      xmlStream = new BufferedInputStream(xmlUrl.openStream());
     
      return XmlUtils.loadXml(new InputSource(xmlStream), XmlUtils.loadSchema(schemaUrl), false);
    } catch (Exception ex) {
      throw new JuException(String.format("Couldn't load XML from URL: %s (Schema URL: %s)",
          xmlUrl, schemaUrl), ex);
    } finally {
      IOUtil.close(xmlStream);
    }
  }

  /**
   * Loads and parses an XML from an InputStream into a DOM structure.
   * <p>
   * The input stream will be wrapped in a BufferedInputStream and closed at the end.
   * @param inputStream InputStream of the XML
   * @param schema Optional Schema. If null, no validation is performed
   * @return Document instance
   * @throws JuException If the XML cannot be loaded or fails validation
   */
  public static Document loadXml(InputStream inputStream, Schema schema) throws JuException {
    InputStream xmlStream = null;
   
    try {
      xmlStream = new BufferedInputStream(inputStream);     
      return XmlUtils.loadXml(new InputSource(xmlStream), schema, false);
    } catch (Exception ex) {
      throw new JuException("Couldn't load XML from InputStream", ex);
    } finally {
      IOUtil.close(xmlStream);
    }
  }
 
  /**
   * Loads and parses an XML into a DOM structure.
   * @param xmlStream InputStream of the XML
   * @param schema Optional Schema. If null, no validation is performed
   * @return Document instance
   * @throws JuException If the XML cannot be loaded or fails validation
   */
  public static Document loadXml(InputSource xmlSource, Schema schema, boolean nameSpaceAware) throws JuException {
    try {
      DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
      docBuilderFactory.setNamespaceAware(nameSpaceAware);
     
      // There's a bug in JDK >= 6, ignoring whitespace is not working
      // docBuilderFactory.setIgnoringElementContentWhitespace(true);
      DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();

      // Parse XML
      Document doc = docBuilder.parse(xmlSource);

      // Remove whitespace manually
      XmlUtils.removeWhitespaceNodes(doc.getDocumentElement());
     
      // Validate if we have a Schema
      if (schema != null) {
        schema.newValidator().validate(new DOMSource(doc));
      }

      return doc;
    } catch (Exception ex) {
      throw new JuException("Couldn't load XML" , ex);
    }
  }
 
  /**
   * Removes all Whitespace Noted from the specified element.
   * @param e Element
   */
  private static void removeWhitespaceNodes(Element e) {
    NodeList children = e.getChildNodes();
    for (int i = children.getLength() - 1; i >= 0; i--) {
      Node child = children.item(i);
      if (child instanceof Text
          && ((Text) child).getData().trim().length() == 0) {
        e.removeChild(child);
      } else if (child instanceof Element) {
        removeWhitespaceNodes((Element) child);
      }
    }
  }
 
  /**
   * Loads an XML from a String.
   * @param xmlString XML String
   * @param schema Optional Schema. If null, no validation is performed
   * @return Document instance
   * @throws JuException If the String cannot be converted to a DOM Document
   */
  public static Document loadXml(String xmlString, Schema schema) throws JuException {
    return XmlUtils.loadXml(new InputSource(new StringReader(xmlString)), schema, false);
  }
 
  /**
   * Loads an XML from a String.
   * @param xmlString XML String
   * @param schema Optional Schema. If null, no validation is performed
   * @param nameSpaceAware whether the Document should be namespace aware
   * @return Document instance
   * @throws JuException If the String cannot be converted to a DOM Document
   */
  // Introduced this method as we had problems in ESW to validate a document unless it was nameSpaceAware...
  public static Document loadXml(String xmlString, Schema schema, boolean nameSpaceAware) throws JuException {
    return XmlUtils.loadXml(new InputSource(new StringReader(xmlString)), schema, nameSpaceAware);
  }
 
  /**
   * Loads an XML Schema from the specified url.
   * @param url URL to load schema from
   * @return Schema instance or null if the url is null
   */
  public static Schema loadSchema(URL url) {
    if (url == null) return null;
   
    try {
      SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
      Schema schema = schemaFactory.newSchema(url);

      return schema;
    } catch (Exception ex) {
      throw new JuRuntimeException("Couldn't load XML schema: " + url, ex);
    }
  }
 
  /**
   * Indents the specified XML.
   * @param xmlString XML string to be indented
   * @param includeXmlDeclaration If true, the &lt;?xml ... ?> declaration is included.
   * @return Indented XML
   */
  public static String indentXml(String xmlString, boolean includeXmlDeclaration) throws JuException {
    Document doc = XmlUtils.loadXml(xmlString, null);
    return XmlUtils.toString(doc, includeXmlDeclaration, true);
  }
 
  /**
   * Converts an XML to a String.
   * <p>
   * This will create an XML string with encoding="UTF-8" and a standalone declaration,
   * as specified in the Document.
   * <p>
   * Indentation will be two blanks - if true.
   * @param document XML Document
   * @param includeXmlDeclaration If true, the &lt;?xml ... ?> declaration is included.
   * @param indent If true, result will be indented (pretty-printed), using two blanks
   * for child indentation
   * @return String representation of the XML
   * @throws JuRuntimeException If the conversion fails
   */
  public static String toString(Document document, boolean includeXmlDeclaration, boolean indent) throws JuRuntimeException {
    try {
      TransformerFactory tf = TransformerFactory.newInstance();
      Transformer transformer = tf.newTransformer();
      transformer.setOutputProperty(OutputKeys.STANDALONE, "yes");
      transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
      transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, (includeXmlDeclaration ? "no" : "yes"));
     
      if (indent) {
        transformer.setOutputProperty(OutputKeys.INDENT, (indent ? "yes" : "no"));
        transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
      }
     
      try (ByteArrayOutputStream os = new ByteArrayOutputStream();) {
        transformer.transform(new DOMSource(document), new StreamResult(os));
        return new String(os.toByteArray(), "UTF-8");
      }     
    } catch (Exception ex) {
      throw new JuRuntimeException("Couldn't convert XML to String", ex);
    }
  }
 
  /**
   * Creates a new XmlBuilder to construct an XML document based on DOM (document
   * object model).
   * @param rootElementName Name of the root element
   * @return XmlBuilder of the root element to construct the XML
   */
  public static XmlBuilder buildXml(String rootElementName) {
    return XmlBuilder.createRootBuilder(rootElementName, null, null);
  }
 
  /**
   * Creates a new XmlBuilder to construct an XML document based on DOM (document
   * object model) using the specified namespace
   * @param rootElementName Name of the root element
   * @return XmlBuilder of the root element to construct the XML
   */
  public static XmlBuilder buildXml(String rootElementName, String namespacePrefix, String namespaceUri) {
    return XmlBuilder.createRootBuilder(rootElementName, namespacePrefix, namespaceUri);
  }

   
//    private static class SchemaLoader {
//      @SuppressWarnings("unchecked") // TODO: Use new apache commons collection API
//      private final Map<URL, Schema> schemaCache = new LRUMap(XmlUtils.SCHEMA_CACHE_MAX_SIZE);
//     
//      synchronized Schema getSchema(URL schemaUrl) throws ServiceDbRuntimeException {       
//        // Try to get the schema
//        Schema schema = this.schemaCache.get(schemaUrl);
//       
//        try {
//          if (schema == null) {
//            _log.info(String.format("Loading XML Schema: %s (current cache size: %d)", schemaUrl, schemaCache.size()));
//                  SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
//                  schema = schemaFactory.newSchema(schemaUrl);
//                  this.schemaCache.put(schemaUrl, schema);
//          }
//         
//          return schema;
//        } catch (Exception ex) {
//          throw new ServiceDbRuntimeException("Couldn't load XML Schema: " + schemaUrl, ex);
//        }
//      }
//    }
   
    /**
     * Returns a MarshallerBuilder that can be used to perform XML marshalling and unmarshalling.
     * @return
     */
    public static MarshallerBuilder marshaller() {
      return new MarshallerBuilder();
    }
   
    public static class MarshallerBuilder {
      private final Logger logger = LoggerFactory.getLogger(MarshallerBuilder.class);
     
      private boolean cacheJaxbContext = true;
      private static LoadingCache<String, JAXBContext> cache;
      private static long MAX_CACHE_SIZE = 100L;
     
      private boolean formattedOutput = false;
      private Schema schema;
     
      private JuNamespacePrefixMapper prefixMapper;
      private NamespacePrefixMapperAdapter prefixMapperAdapter;
     
      /**
       * Whether to produce formatted output.
       * <p>
       * Default is false.
       * @param formattedOutput If true, XML output will be formatted / indented.
       * @return
       */
      public MarshallerBuilder formattedOutput(boolean formattedOutput) {
        this.formattedOutput = formattedOutput;
        return this;
      }
     
      /**
       * Specifies an XML Schema to be used to validate the XML when marshalling or
       * unmarshalling.
       * @param schemaUrl URL to XML Schema (XSD)
       * @return
       */
      public MarshallerBuilder schema(URL schemaUrl) {
        this.schema = XmlUtils.loadSchema(schemaUrl);
        return this;
      }
     
      /**
       * Sets the prefix for the specified namespace URI.
       * <p>
       * <strong>NOTE:</strong> This relies on an SUN internal implementation class of JAXB so it might not
       * work on all JDKs. If platform independent behavior is required, you should specify the namespaces
       * using the package annotation <i>package-info.java</i>, e.g.
       * <pre>
       * <code>
       * {@literal @}javax.xml.bind.annotation.XmlSchema(
     * namespace = "urn:inftec.ch/ju/util/xml/ns/main"
     * , elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED
     * , xmlns = {
     * {@literal @}XmlNs(namespaceURI = "urn:inftec.ch/ju/util/xml/ns/main", prefix="def")}
     * )
     * package ch.inftec.ju.util.xml.ns.main;
       * import javax.xml.bind.annotation.XmlNs;
     * </code>
     * </pre>
       * @param prefix Namespace prefix, e.g. 'ns'. Use null for default namespace
       * @param namespaceUri Namespace URI, e.g. 'urn:inftec.ch/ns'
       * @return
       */
      public MarshallerBuilder setNamespacePrefix(String prefix, String namespaceUri) {
        if (this.prefixMapper == null) {
          this.prefixMapper = new JuNamespacePrefixMapper();
        }
       
        this.prefixMapper.setPrefix(prefix, namespaceUri);
       
        return this;
      }
     
      /**
       * Sets a NamespacePrefixMapper adapter. This can/must be used if the JAXB implementation in use is not compatible
       * with the JAXB implementation used by JU.
       * @param prefixMapperAdapter
       * @return
       */
      public MarshallerBuilder setNamespacePrefixMapper(NamespacePrefixMapperAdapter prefixMapperAdapter) {
        this.prefixMapperAdapter = prefixMapperAdapter;
        return this;
      }
     
      /**
       * Marshals the specified Object to a String, i.e. converts it to
       * an XML.
       * @param o Object to be marshalled
       * @return XML String
       */
      public String marshalToString(Object o) {
          try (StringWriter writer = new StringWriter()) {
                Object obj = o;
                if (o instanceof JAXBElement) {
                  JAXBElement<?> e = (JAXBElement<?>)o;
                  obj = e.getValue();
                }
               
                logger.debug("Marshalling " + obj.getClass());
               
                JAXBContext context = this.loadContext(obj.getClass().getPackage().getName());
                javax.xml.bind.Marshaller marshaller = context.createMarshaller();
               
                // Use the Schema to make sure the marshalled String is valid according the the Schema (if any)
                marshaller.setSchema(this.schema);
               
                // Configure Marshaller
               
                if (this.formattedOutput) {
                    marshaller.setProperty(javax.xml.bind.Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
                }
               
                if (this.prefixMapper != null) {
                  // Use the default namespace prefix mapper adapter if none has been specified explicitly
                  AssertUtil.assertNotNull("Prefix Mapper must be specified when using prefix mapping", this.prefixMapperAdapter);
//                  if (this.prefixMapperAdapter == null) {
//                    AssertUtil.
//                    this.prefixMapperAdapter = new DefaultNamespacePrefixMapperAdapter();
//                  }
                 
                  marshaller.setProperty(
                      this.prefixMapperAdapter.getPropertyName()
                      , this.prefixMapperAdapter.getNamespacePrefixMapperImplementation(this.prefixMapper));
                }
               
               
                marshaller.marshal(o, writer);
       
                String xmlString = writer.toString();

                logger.debug("Marshalling done");
                return xmlString;
            } catch (Exception ex) {
              throw new JuRuntimeException("Couldn't marshal object %s (using Schema: %s)", ex, o, null);//this.schema), ex);
            }
      }
     
        /**
         * Unmarshals the specified XML string, i.e. converts it into an object instance.
         * <p>
         * Use this method if you're not sure which objects you will get.
         * @param xmlString XML string to be unmarshalled
         * @param objClass Object of the same package as the expected object, used to
         * initialize the JAXBContext.
         * @return Unmarshalled object. If by unmarshalling, we get a JAXBElement, the actual object
         * will be unwrapped and returned
         * @throws ServiceDbException If the unmarshalling fails
         */
        public Object unmarshalRaw(String xmlString, Class<?> objClass) {
            try (StringReader reader = new StringReader(xmlString)) {
                JAXBContext context = this.loadContext(objClass.getPackage().getName());
                Unmarshaller unmarshaller = context.createUnmarshaller();
               
                // Set Schema to validate (if any)
                unmarshaller.setSchema(this.schema);
               
                Object obj = unmarshaller.unmarshal(reader);               
                if (obj instanceof JAXBElement) {
                  // Unwrap object
                  obj = ((JAXBElement<?>)obj).getValue();
                }
               
                return obj;
            } catch (Exception ex) {
              logger.debug("Marshalling of String failed: " + xmlString);
                throw new JuRuntimeException("Couldn't unmarshal XML String (XML is dumped as debug log)", ex);
            }
        }
       
        private JAXBContext loadContext(String contextPath) throws JAXBException {
          if (this.cacheJaxbContext) {
            // We'll cache the JAXBContext to avoid loading it every time we need it.
            // JAXBContext is ThreadSave, but we'll need to create a separate Marshaller / Unmarshaller
            // every time we do marshalling / unmarshalling and must not use the
            // convenience method context.marshal / unmarshal.
            synchronized(this) {
              if (cache == null) {
                cache = CacheBuilder.newBuilder()
                  .maximumSize(MAX_CACHE_SIZE)
                  .build(new CacheLoader<String, JAXBContext>() {
                @Override
                public JAXBContext load(String key) throws Exception {
                  return createContext(key);
                }
                  });
              }
            }
           
            try {
              return cache.get(contextPath);
            } catch (ExecutionException ex) {
              throw new JuRuntimeException("Couldn't load new JAXBContext for %s", ex, contextPath);
            }
          } else {
            return createContext(contextPath);
          }
        }
       
        private JAXBContext createContext(String contextPath) throws JAXBException {
          logger.debug("Creating JAXBContext for path {}", contextPath);
          return JAXBContext.newInstance(contextPath);
        }
       
        /**
         * Unmarshals the specified XML string, i.e. converts it into an object instance.
         * @param xmlString XML string to be unmarshalled
         * @param objClass Expected Object class used to build JAXBContext and evaluate validation Schema
         * @return Unmarshalled object
         * @throws ServiceDbException If the unmarshalling fails
         * @param <T> Type of expected object
         */
        public <T> T unmarshal(String xmlString, Class<T> objClass) {
            @SuppressWarnings("unchecked")
            T object = (T)this.unmarshalRaw(xmlString, objClass);
           
            return object;
        }
       
        /**
         * Unmarshals the specified XML, i.e. converts it into an object instance.
         * @param xmlUrl URL to XML to be unmarshalled
         * @param objClass Expected Object class used to build JAXBContext and evaluate validation Schema
         * @return Unmarshalled object
         * @throws ServiceDbException If the unmarshalling fails
         * @param <T> Type of expected object
         */
        public <T> T unmarshal(URL xmlUrl, Class<T> objClass) {
          String xmlString = new IOUtil().loadTextFromUrl(xmlUrl);
         
            @SuppressWarnings("unchecked")
            T object = (T)this.unmarshalRaw(xmlString, objClass);
           
            return object;
        }
       
        /**
         * Helper interface to provide a JAXB implementation specific instance of the NamespacePrefixMapper.
         * @author Martin Meyer <martin.meyer@inftec.ch>
         *
         */
        public interface NamespacePrefixMapperAdapter {
          /**
           * Gets the property name for the NamespacePrefixMapper property of the JAXB marshaller.
           * @return
           */
          public String getPropertyName();
         
          /**
           * Gets an implementation of the NamespacePrefixMapper.
           * <p>
           * A JU specific PrefixMapper will be provided as a parameter, so the call to getPreferredPrefix
           * can just be forwarded to this mapper.
           * @param prefixMapper JU specific Prefix mapper
           * @return JAXB implementation specific mapper
           */
          public Object getNamespacePrefixMapperImplementation(PrefixMapper prefixMapper);
         
          /**
           * JU specific prefix mapper.
           * @author Martin Meyer <martin.meyer@inftec.ch>
           *
           */
          public interface PrefixMapper {
            /**
             * Evaluates the preferred prefix
             * @param namespaceUri XSD namespace URI
             * @param suggestion Suggested prefix
             * @param requirePrefix If a prefix is required
             * @return Preferred prefix
             */
            String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix);
          }
        }
    }
   
    /**
     * Validates the specified XML String using the XSD Schema.
     * @param xml XML to validate
     * @param schema Schema instance
     * @throws JuException If the validation fails
     */
    public static void validate(String xml, Schema schema) throws JuException {
      try {
        schema.newValidator().validate(new DOMSource(XmlUtils.loadXml(xml, null)));
      } catch (SAXParseException ex) {
        throw new JuException("Parse exception: " + ex.getMessage(), ex);
      } catch (Exception ex) {
        throw new JuException("Validation failed", ex);
      }
    }
   
    /**
     * Validates the specified XML String using the XSD Schema.
     * @param xml XML to validate
     * @param schema Schema instance
     * @param nameSpaceAware Whether validation should be namespace aware
     * @throws JuException If the validation fails
     */
    public static void validate(String xml, Schema schema, boolean nameSpaceAware) throws JuException {
      try {
        schema.newValidator().validate(new DOMSource(XmlUtils.loadXml(xml, null, nameSpaceAware)));
      } catch (SAXParseException ex) {
        throw new JuException("Parse exception: " + ex.getMessage(), ex);
      } catch (Exception ex) {
        throw new JuException("Validation failed", ex);
      }
    }
   
    /**
     * Converts a java.util.Date into an instance of XMLGregorianCalendar.
     * <p>
     * The timezone of the calendar will be set to GMT to enforce Zulu-Time-Format
     * in the XML messages.
     * @param date Instance of java.util.Date or a null reference
     * @return XMLGregorianCalendar instance whose value is based upon the
     *  value in the date parameter. If the date parameter is null then
     *  this method will simply return null.
     */
    public static XMLGregorianCalendar asXMLGregorianCalendar(java.util.Date date) {
        if (date == null) {
            return null;
        } else {
            GregorianCalendar gc = new GregorianCalendar();
            gc.setTime(date);
            gc.setTimeZone(TimeZone.getTimeZone("GMT")); // Set time zone to GMT to enforce Zulu-Time in XML
            return getDatetypeFactory().newXMLGregorianCalendar(gc);
        }
    }

    /**
     * Converts an XMLGregorianCalendar to an instance of java.util.Date
     *
     * @param xgc Instance of XMLGregorianCalendar or a null reference
     * @return java.util.Date instance whose value is based upon the
     *  value in the xgc parameter. If the xgc parameter is null then
     *  this method will simply return null.
     */
    public static java.util.Date asDate(XMLGregorianCalendar xgc) {
        if (xgc == null) {
            return null;
        } else {
            return xgc.toGregorianCalendar().getTime();
        }
    }
}
TOP

Related Classes of ch.inftec.ju.util.xml.XmlUtils$MarshallerBuilder$NamespacePrefixMapperAdapter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.