de.interactive_instruments.ShapeChange.Target.Metadata.ApplicationSchemaMetadata.java Source code

Java tutorial

Introduction

Here is the source code for de.interactive_instruments.ShapeChange.Target.Metadata.ApplicationSchemaMetadata.java

Source

/**
 * ShapeChange - processing application schemas for geographic information
 *
 * This file is part of ShapeChange. ShapeChange takes a ISO 19109
 * Application Schema from a UML model and translates it into a
 * GML Application Schema or other implementation representations.
 *
 * Additional information about the software can be found at
 * http://shapechange.net/
 *
 * (c) 2002-2016 interactive instruments GmbH, Bonn, Germany
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Contact:
 * interactive instruments GmbH
 * Trierer Strasse 70-72
 * 53115 Bonn
 * Germany
 */
package de.interactive_instruments.ShapeChange.Target.Metadata;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.io.FileUtils;
import org.apache.xml.serializer.OutputPropertiesFactory;
import org.apache.xml.serializer.Serializer;
import org.apache.xml.serializer.SerializerFactory;
import org.w3c.dom.Attr;
import org.w3c.dom.Comment;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import de.interactive_instruments.ShapeChange.MessageSource;
import de.interactive_instruments.ShapeChange.Options;
import de.interactive_instruments.ShapeChange.ProcessMapEntry;
import de.interactive_instruments.ShapeChange.ShapeChangeAbortException;
import de.interactive_instruments.ShapeChange.ShapeChangeResult;
import de.interactive_instruments.ShapeChange.Model.ClassInfo;
import de.interactive_instruments.ShapeChange.Model.Info;
import de.interactive_instruments.ShapeChange.Model.Model;
import de.interactive_instruments.ShapeChange.Model.PackageInfo;
import de.interactive_instruments.ShapeChange.Model.PropertyInfo;
import de.interactive_instruments.ShapeChange.TargetIdentification;
import de.interactive_instruments.ShapeChange.Target.SingleTarget;
import de.interactive_instruments.ShapeChange.Transformation.Profiling.ProfileIdentifierMap;
import de.interactive_instruments.ShapeChange.Transformation.Profiling.MalformedProfileIdentifierException;
import de.interactive_instruments.ShapeChange.Transformation.Profiling.ProfileIdentifier.IdentifierPattern;
import de.interactive_instruments.ShapeChange.UI.StatusBoard;

/**
 * @author Johannes Echterhoff (echterhoff <at> interactive-instruments
 *         <dot> de)
 *
 */
public class ApplicationSchemaMetadata implements SingleTarget, MessageSource {

    public static final String NS = "http://shapechange.net/targets/ApplicationSchemaMetadata";

    public static int STATUS_RULE_ALL_IDENTIFY_PROFILES = 301500;

    /* ------------------------------------------- */
    /* --- configuration parameter identifiers --- */
    /* ------------------------------------------- */

    // none at present

    /* ------------------------ */
    /* --- rule identifiers --- */
    /* ------------------------ */

    /**
     * This rule identifies the names of all the profiles to which elements
     * (classes and properties) in selected schemas belong. Identified profiles
     * are listed within ProfilesMetadata/containedProfile elements.
     */
    public static final String RULE_ALL_IDENTIFY_PROFILES = "rule-asm-all-identify-profiles";

    /* --------------------- */
    /* --- tagged values --- */
    /* --------------------- */

    // none at present

    /* -------------------- */
    /* --- other fields --- */
    /* -------------------- */

    private static boolean initialised = false;

    protected static Document document = null;
    protected static Element root = null;
    protected static Map<String, ProcessMapEntry> mapEntryByType = new HashMap<String, ProcessMapEntry>();

    private static Comment hook;

    private static File outputDirectoryFile;
    private static String outputDirectory;
    private static String outputFilename;

    private static boolean printed = false;
    private static boolean diagnosticsOnly = false;

    private static String schemaTargetNamespace = null;

    private ShapeChangeResult result = null;
    private PackageInfo schemaPi = null;
    private Model model = null;
    private Options options = null;

    @Override
    public void initialise(PackageInfo p, Model m, Options o, ShapeChangeResult r, boolean diagOnly)
            throws ShapeChangeAbortException {

        schemaPi = p;
        schemaTargetNamespace = p.targetNamespace();

        model = m;
        options = o;
        result = r;
        diagnosticsOnly = diagOnly;

        result.addDebug(this, 1, schemaPi.name());

        if (!initialised) {
            initialised = true;

            outputDirectory = options.parameter(this.getClass().getName(), "outputDirectory");
            if (outputDirectory == null)
                outputDirectory = options.parameter("outputDirectory");
            if (outputDirectory == null)
                outputDirectory = options.parameter(".");

            outputFilename = "schema_metadata.xml";

            // Check if we can use the output directory; create it if it
            // does not exist
            outputDirectoryFile = new File(outputDirectory);
            boolean exi = outputDirectoryFile.exists();
            if (!exi) {
                try {
                    FileUtils.forceMkdir(outputDirectoryFile);
                } catch (IOException e) {
                    result.addError(null, 600, e.getMessage());
                    e.printStackTrace(System.err);
                }
                exi = outputDirectoryFile.exists();
            }
            boolean dir = outputDirectoryFile.isDirectory();
            boolean wrt = outputDirectoryFile.canWrite();
            boolean rea = outputDirectoryFile.canRead();
            if (!exi || !dir || !wrt || !rea) {
                result.addFatalError(null, 601, outputDirectory);
                throw new ShapeChangeAbortException();
            }

            File outputFile = new File(outputDirectoryFile, outputFilename);

            // check if output file already exists - if so, attempt to delete it
            exi = outputFile.exists();
            if (exi) {

                result.addInfo(this, 3, outputFilename, outputDirectory);

                try {
                    FileUtils.forceDelete(outputFile);
                    result.addInfo(this, 4);
                } catch (IOException e) {
                    result.addInfo(null, 600, e.getMessage());
                    e.printStackTrace(System.err);
                }
            }

            // identify map entries defined in the target configuration
            List<ProcessMapEntry> mapEntries = options.getCurrentProcessConfig().getMapEntries();

            if (mapEntries != null) {

                for (ProcessMapEntry pme : mapEntries) {
                    mapEntryByType.put(pme.getType(), pme);
                }
            }

            // reset processed flags on all classes in the schema
            for (Iterator<ClassInfo> k = model.classes(schemaPi).iterator(); k.hasNext();) {
                ClassInfo ci = k.next();
                ci.processed(getTargetID(), false);
            }

            // ======================================
            // Parse configuration parameters
            // ======================================

            // nothing to do at present

            // ======================================
            // Set up the document and create root
            // ======================================

            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            dbf.setNamespaceAware(true);
            dbf.setValidating(true);
            dbf.setAttribute(Options.JAXP_SCHEMA_LANGUAGE, Options.W3C_XML_SCHEMA);
            DocumentBuilder db;
            try {
                db = dbf.newDocumentBuilder();
            } catch (ParserConfigurationException e) {
                result.addFatalError(null, 2);
                throw new ShapeChangeAbortException();
            }
            document = db.newDocument();

            root = document.createElementNS(NS, "ApplicationSchemaMetadata");
            document.appendChild(root);

            addAttribute(root, "xmlns", NS);

            hook = document.createComment("Created by ShapeChange - http://shapechange.net/");
            root.appendChild(hook);
        }

        // create elements documenting the application schema
        Element e_name = document.createElement("name");
        e_name.setTextContent(schemaPi.name());
        Element e_tns = document.createElement("targetNamespace");
        e_tns.setTextContent(schemaPi.targetNamespace());

        Element e_as = document.createElement("ApplicationSchema");
        e_as.appendChild(e_name);
        e_as.appendChild(e_tns);

        Element e_schema = document.createElement("schema");
        e_schema.appendChild(e_as);

        root.appendChild(e_schema);

        /*
         * Now compute relevant metadata.
         */
        processMetadata(e_as);
    }

    protected void processMetadata(Element appSchemaElement) {

        if (schemaPi != null) {

            if (schemaPi.matches(RULE_ALL_IDENTIFY_PROFILES)) {

                StatusBoard.getStatusBoard().statusChanged(STATUS_RULE_ALL_IDENTIFY_PROFILES);

                processProfilesMetadata(appSchemaElement);
            }

            // add additional rules to compute metadata here
        }

    }

    protected void processProfilesMetadata(Element appSchemaElement) {

        Set<Info> schemaElements = new TreeSet<Info>();

        // identify all classes and properties that belong to the schema
        SortedSet<ClassInfo> schemaClasses = model.classes(schemaPi);

        if (schemaClasses != null) {

            for (ClassInfo ci : schemaClasses) {
                schemaElements.add(ci);

                for (PropertyInfo pi : ci.properties().values()) {
                    schemaElements.add(pi);
                }
            }
        }

        // identify the profiles of all relevant schema elements
        SortedSet<String> profileNames = new TreeSet<String>();

        for (Info i : schemaElements) {

            String[] profilesTVs = i.taggedValuesForTag("profiles");

            for (String profilesTV : profilesTVs) {

                if (profilesTV.trim().length() > 0) {

                    try {

                        ProfileIdentifierMap piMap = ProfileIdentifierMap.parse(profilesTV, IdentifierPattern.loose,
                                i.name());

                        profileNames.addAll(piMap.getProfileIdentifiersByName().keySet());

                    } catch (MalformedProfileIdentifierException e) {
                        result.addWarning(this, 9, profilesTV, i.fullNameInSchema(), e.getMessage());
                    }
                }
            }
        }

        // now create the ProfilesMetadata XML element
        Element e_pm = document.createElement("ProfilesMetadata");

        for (String name : profileNames) {
            Element e_cp = document.createElement("containedProfile");
            e_cp.setTextContent(name);
            e_pm.appendChild(e_cp);
        }

        Element e_m = document.createElement("metadata");

        e_m.appendChild(e_pm);

        appSchemaElement.appendChild(e_m);
    }

    /** Add attribute to an element */
    protected void addAttribute(Element e, String name, String value) {
        Attr att = document.createAttribute(name);
        att.setValue(value);
        e.setAttributeNode(att);
    }

    @Override
    public void process(ClassInfo ci) {

        /*
         * This target gathers metadata about an application schema as-is. As
         * such, it can do so directly with the package provided by the
         * initialize method. Ordering of classes is handled directly by this
         * class.
         * 
         * NOTE: the Converter.STATUS_TARGET_PROCESS can therefore be ignored
         * for this target.
         */
    }

    @Override
    public void write() {

        // nothing to do here
    }

    @Override
    public int getTargetID() {
        return TargetIdentification.APP_SCHEMA_METADATA.getId();
    }

    @Override
    public String message(int mnr) {

        switch (mnr) {
        case 0:
            return "Context: class ApplicationSchemaMetadata";
        case 1:
            return "Retrieving metadata for application schema '$1$'.";
        case 2:
            return "XML Schema document with name '$1$' could not be created, invalid filename.";
        case 3:
            return "Could not write output to file '$1$'. Exception message is: $2$.";
        case 4:
            return "File has been deleted.";
        case 5:
            return ""; // unused (moved to ShapeChangeResult)
        case 6:
            return "Processing class '$1$'.";
        case 7:
            return "Class '$1$' is a $2$ which is not supported by this target. The class will be ignored.";
        case 8:
            return "Number format exception while converting the value of configuration parameter '$1$' to an integer. Exception message: $2$. The parameter will be ignored.";
        case 9:
            return "The profile identifier '$1$' in model element '$2$' is not well-formed: $3$";

        case 100:
            return "Context: property '$1$' in class '$2$'.";
        default:
            return "(Unknown message)";
        }
    }

    @Override
    public void writeAll(ShapeChangeResult r) {

        if (printed || diagnosticsOnly) {
            return;
        }

        Properties outputFormat = OutputPropertiesFactory.getDefaultMethodProperties("xml");
        outputFormat.setProperty("indent", "yes");
        outputFormat.setProperty("{http://xml.apache.org/xalan}indent-amount", "2");
        outputFormat.setProperty("encoding", "UTF-8");

        /*
         * Uses OutputStreamWriter instead of FileWriter to set character
         * encoding (see doc in Serializer.setWriter and FileWriter)
         */
        try {
            File repXsd = new File(outputDirectoryFile, outputFilename);

            BufferedWriter writer = new BufferedWriter(
                    new OutputStreamWriter(new FileOutputStream(repXsd), "UTF-8"));

            Serializer serializer = SerializerFactory.getSerializer(outputFormat);
            serializer.setWriter(writer);
            serializer.asDOMSerializer().serialize(document);

            writer.close();

            r.addResult(getTargetID(), outputDirectory, outputFilename, schemaTargetNamespace);

        } catch (IOException ioe) {

            r.addError(this, 3, outputFilename, ioe.getMessage());
        }

        printed = true;
    }

    @Override
    public void reset() {

        initialised = false;

        schemaTargetNamespace = null;

        outputDirectoryFile = null;
        outputDirectory = null;
        outputFilename = null;

        printed = false;
        diagnosticsOnly = false;

        document = null;
        root = null;
        hook = null;
        mapEntryByType = new HashMap<String, ProcessMapEntry>();
    }

}