EMFUML2RepositoryFacade.java

package org.andromda.repositories.emf.uml22;

import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import org.andromda.core.common.ComponentContainer;
import org.andromda.core.metafacade.ModelAccessFacade;
import org.andromda.core.repository.RepositoryFacadeException;
import org.andromda.metafacades.emf.uml22.UMLModelAccessFacade;
import org.andromda.metafacades.emf.uml22.UmlUtilities;
import org.andromda.repositories.emf.EMFRepositoryFacade;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.EPackage.Registry;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.URIConverter;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl;
import org.eclipse.emf.ecore.xmi.impl.XMLParserPoolImpl;
import org.eclipse.emf.mapping.ecore2xml.Ecore2XMLPackage;
import org.eclipse.uml2.types.TypesPackage;
import org.eclipse.uml2.uml.Model;
import org.eclipse.uml2.uml.UMLPackage;
import org.eclipse.uml2.uml.profile.standard.StandardPackage;
import org.eclipse.uml2.uml.resource.UML22UMLExtendedMetaData;
import org.eclipse.uml2.uml.resource.UML22UMLResource;
import org.eclipse.uml2.uml.resource.UMLResource;
import org.eclipse.uml2.uml.resource.XMI2UMLResource;

/**
 * Implements an AndroMDA object model repository by using the <a
 * href="http://www.eclipse.org/uml2/">Eclipse UML2 API set </a>.
 *
 * @author Steve Jerman
 * @author Chad Brandon
 * @author Matthias Bohlen (native IBM RSM file reading)
 * @author Bob Fields (Multiple model support, RSM Profiles)
 */
public class EMFUML2RepositoryFacade extends EMFRepositoryFacade
{
    /**
     * The logger instance.
     */
    private static final Logger logger = Logger.getLogger(EMFUML2RepositoryFacade.class);

    /**
     * Perform required registrations for EMF/UML2.
     *
     * @see org.andromda.core.repository.RepositoryFacade#open()
     */
    @Override
    public ResourceSet createNewResourceSet()
    {
        if (logger.isDebugEnabled())
        {
            logger.debug("Registering resource factories");
        }

        // Use our own proxy resolver which extends the standard UML2 resolver, to load moduleSearchLocations URLs
        final ResourceSet proxyResourceSet = new EMXProxyResolvingResourceSet();

        // - we need to perform these registrations in order to load a UML model into EMF
        //   see: http://dev.eclipse.org/viewcvs/indextools.cgi/%7Echeckout%7E/uml2-home/faq.html#6 OR http://wiki.eclipse.org/MDT/UML2/FAQ
        Registry packageRegistry = proxyResourceSet.getPackageRegistry();
        // EcorePackage.eNS_URI=http://www.eclipse.org/emf/2002/Ecore
        packageRegistry.put(EcorePackage.eNS_URI, EcorePackage.eINSTANCE);
        // UMLPackage.eNS_URI=http://www.eclipse.org/uml2/2.1.0/UML
        // This gives a ConnectException when loading the model unless 2.0.0 namespace is also registered
        packageRegistry.put(UMLPackage.eNS_URI, UMLPackage.eINSTANCE);
        //packageRegistry.put("http://www.eclipse.org/uml2/2.1.0/UML", UMLPackage.eINSTANCE);
        // register the UML package from org.eclipse.uml2
        packageRegistry.put("http://www.eclipse.org/uml2/1.0.0/UML", UMLPackage.eINSTANCE);
        packageRegistry.put("http://www.eclipse.org/uml2/2.0.0/UML", UMLPackage.eINSTANCE);
        packageRegistry.put("http://www.eclipse.org/uml2/2.1.0/UML", UMLPackage.eINSTANCE);
        packageRegistry.put("http://www.eclipse.org/uml2/2.2.0/UML", UMLPackage.eINSTANCE);
        packageRegistry.put("http://www.eclipse.org/uml2/2.3.0/UML", UMLPackage.eINSTANCE);
        packageRegistry.put("http://www.eclipse.org/uml2/3.0.0/UML", UMLPackage.eINSTANCE);
        packageRegistry.put("http://www.eclipse.org/uml2/3.1.0/UML", UMLPackage.eINSTANCE);
        packageRegistry.put("http://www.eclipse.org/uml2/4.0.0/UML", UMLPackage.eINSTANCE);
        packageRegistry.put("http://www.eclipse.org/uml2/5.0.0/UML", UMLPackage.eINSTANCE);
        packageRegistry.put("http://www.eclipse.org/uml2/4.0.0/Types", TypesPackage.eINSTANCE);
        packageRegistry.put("http://www.eclipse.org/uml2/5.0.0/Types", TypesPackage.eINSTANCE);
        packageRegistry.put("http://www.eclipse.org/uml2/4.0.0/UML/Profile/L2", StandardPackage.eINSTANCE);
        packageRegistry.put("http://www.eclipse.org/uml2/4.0.0/UML/Profile/L3", StandardPackage.eINSTANCE);
        packageRegistry.put("http://www.eclipse.org/uml2/5.0.0/UML/Profile/Standard", StandardPackage.eINSTANCE);
        packageRegistry.put(Ecore2XMLPackage.eNS_URI, Ecore2XMLPackage.eINSTANCE);
        packageRegistry.put(EcorePackage.eNS_URI, EcorePackage.eINSTANCE);
        // register the UML2 schema against the standard UML namespace for UML 2.0 and 2.1
        // see: http://dev.eclipse.org/newslists/news.eclipse.tools.uml2/msg03392.html
        packageRegistry.put("http://schema.omg.org/spec/XMI/2.0", UMLPackage.eINSTANCE);
        packageRegistry.put("http://schema.omg.org/spec/XMI/2.0.1", UMLPackage.eINSTANCE);
        packageRegistry.put("http://schema.omg.org/spec/XMI/2.1", UMLPackage.eINSTANCE);
        packageRegistry.put("http://schema.omg.org/spec/XMI/2.1.1", UMLPackage.eINSTANCE);
        packageRegistry.put("http://schema.omg.org/spec/XMI/2.1.2", UMLPackage.eINSTANCE);
        packageRegistry.put("http://schema.omg.org/spec/XMI/2.2", UMLPackage.eINSTANCE);
        packageRegistry.put("http://schema.omg.org/spec/XMI/2.3", UMLPackage.eINSTANCE);
        packageRegistry.put("http://schema.omg.org/spec/XMI/2.4", UMLPackage.eINSTANCE);
        packageRegistry.put("http://schema.omg.org/spec/XMI/2.4.1", UMLPackage.eINSTANCE);
        packageRegistry.put("http://schema.omg.org/spec/UML/2.0", UMLPackage.eINSTANCE);
        packageRegistry.put("http://schema.omg.org/spec/UML/2.1", UMLPackage.eINSTANCE);
        packageRegistry.put("http://schema.omg.org/spec/UML/2.1.1", UMLPackage.eINSTANCE);
        packageRegistry.put("http://schema.omg.org/spec/UML/2.1.2", UMLPackage.eINSTANCE);
        packageRegistry.put("http://schema.omg.org/spec/UML/2.2", UMLPackage.eINSTANCE);
        packageRegistry.put("http://schema.omg.org/spec/UML/2.3", UMLPackage.eINSTANCE);
        packageRegistry.put("http://schema.omg.org/spec/UML/2.4", UMLPackage.eINSTANCE);
        packageRegistry.put("http://schema.omg.org/spec/UML/2.4.1", UMLPackage.eINSTANCE);
        packageRegistry.put("http://schema.omg.org/spec/UML/2.5", UMLPackage.eINSTANCE);

        // Maps from file extension to resource for XML deserialization
        final Map<String, Object> extensionToFactoryMap = proxyResourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap();
        //Map extensionToFactoryMap = Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap( );
        // Register all files with all extensions as .uml resources, for loading purposes
        //extensionToFactoryMap.put(Resource.Factory.Registry.DEFAULT_EXTENSION, UMLResource.Factory.INSTANCE);
        // register known file extensions:
        // - *.uml -> native Eclipse/UML2 2.x 3.x 4.x resource
        // - *.uml2 -> native Eclipse/UML2 1.x resource
        // - *.xmi, *.xml -> OMG XMI UML2 resource
        // - *.emx, *.epx -> IBM Rational Modeler UML2 model and profile resource
        extensionToFactoryMap.put(UMLResource.FILE_EXTENSION, UMLResource.Factory.INSTANCE);
        extensionToFactoryMap.put("emx", UMLResource.Factory.INSTANCE);
        extensionToFactoryMap.put("epx", UMLResource.Factory.INSTANCE);
        // Allow loading the old .uml2 UML2 1.x files with UML2 2.x libraries
        extensionToFactoryMap.put("uml2", UML22UMLResource.Factory.INSTANCE);
        //extensionToFactoryMap.put(XMI2UMLResource.FILE_EXTENSION, XMI2UMLResource.Factory.INSTANCE);
        extensionToFactoryMap.put(XMI2UMLResource.FILE_EXTENSION, new XMIResourceFactoryImpl());
        // Allow reading MagicDraw .xml files inside .xml.zip, which are really xmi files.
        extensionToFactoryMap.put("xml", new XMIResourceFactoryImpl());

        // if IBM's metamodel jars are on the classpath, we can register the package factories.
        // This appears to have no effect, emx models are processed anyway.
        //boolean registered =
            registerOptionalRsmMetamodels(proxyResourceSet.getPackageRegistry());
        // RSM profiles Default and Deployment.epx are dependencies referred to by com/ibm/rsm/7.5/pom.
        // UML2 Standard resources are located under org/eclipse/uml2/uml/resources, referred to by metafacade dependency so it is in the plugin classpath.
        // Eclipse examples show URI.create with a hard-coded jar file location like jar:file:/C:/eclipse/plugins/org.eclipse.uml2.uml.resources_2.0.0.v200606221411.jar!/
        // Find the UML2 resources on the plugin classpath and set proxy URI based on actual found location.
        // Classloader changed in maven3, see: https://cwiki.apache.org/MAVEN/maven-3x-class-loading.html. getClassLoader() no longer works.
        URL url = this.getClass().getResource("/libraries/UMLPrimitiveTypes.library.uml");
        // ResourceUtils.getResource, getContextClassLoader does not work for the plugin classloader dependencies in maven3
        if (url!=null)
        {
            // Need to create a pathmap location map for UML2 Resources, to load standard profiles.
            String path = url.getPath().substring(0, url.getPath().indexOf("libraries"));
            URI uri = URI.createURI("jar:" + path);

            // URI umlResourcePluginURI = URI.createURI("jar:file:/" + umlResourcePath + "!/");
            // UML_LIBRARIES referenced by model, UML_METAMODELS referenced by profiles
            // = UMLResource.LIBRARIES_PATHMAP=pathmap://UML_LIBRARIES/
            // UML Resources are loaded from maven org/eclipse/uml2/uml/resources
            URIConverter.URI_MAP.put(URI.createURI(UMLResource.LIBRARIES_PATHMAP),
                uri.appendSegment("libraries").appendSegment(""));
            // UMLResource.METAMODELS_PATHMAP=pathmap://UML_LIBRARIES/
            URIConverter.URI_MAP.put(URI.createURI(UMLResource.METAMODELS_PATHMAP),
                uri.appendSegment("metamodels").appendSegment(""));
            // UMLResource.PROFILES_PATHMAP=UMLResource.PROFILES_PATHMAP
            URIConverter.URI_MAP.put(URI.createURI(UMLResource.PROFILES_PATHMAP),
                uri.appendSegment("profiles").appendSegment(""));
        }
        else
        {
            logger.error("Could not load UML2 org.eclipse.uml2.resources jar from classpath");
        }
        // Local implementation which delegates to the global map, so registrations are local
        Map<URI, URI> uriMap = proxyResourceSet.getURIConverter().getURIMap();
        uriMap.putAll(UML22UMLExtendedMetaData.getURIMap());
        // TODO: Map from UML/2.0 etc to UML later versions. uriMap typically contains pathmap:// values.
        //uriMap.put(URI.createURI("http://schema.omg.org/spec/UML/2.0"),
        //    URI.createURI("http://www.eclipse.org/uml2/1.0.0/UML"));
        uriMap.put(URI.createURI("http://schema.omg.org/spec/UML/2.0"),
                URI.createURI(UMLPackage.eNS_URI));
        uriMap.put(URI.createURI("http://schema.omg.org/spec/UML/2.1"),
                URI.createURI(UMLPackage.eNS_URI));
        uriMap.put(URI.createURI("http://schema.omg.org/spec/UML/2.1.1"),
                URI.createURI(UMLPackage.eNS_URI));
        uriMap.put(URI.createURI("http://schema.omg.org/spec/UML/2.1.2"),
                URI.createURI(UMLPackage.eNS_URI));
        uriMap.put(URI.createURI("http://schema.omg.org/spec/UML/2.2"),
                URI.createURI(UMLPackage.eNS_URI));
        uriMap.put(URI.createURI("http://schema.omg.org/spec/UML/2.3"),
                URI.createURI(UMLPackage.eNS_URI));
        uriMap.put(URI.createURI("http://schema.omg.org/spec/UML/2.4"),
                URI.createURI(UMLPackage.eNS_URI));
        uriMap.put(URI.createURI("http://schema.omg.org/spec/UML/2.4.1"),
                URI.createURI(UMLPackage.eNS_URI));
        uriMap.put(URI.createURI("http://schema.omg.org/spec/UML/2.5"),
                URI.createURI(UMLPackage.eNS_URI));
        uriMap.put(URI.createURI("http://www.eclipse.org/uml2/4.0.0/Types"),
                URI.createURI(TypesPackage.eNS_URI));
        // Add pathmap for RSM UML2_MSL_PROFILES in com/ibm/xtools/uml/msl/7.10.500/msl-7.10.500.jar
        url = this.getClass().getResource("/profiles/Default.epx");
        if (url!=null)
        {
            // Need to create a pathmap location map for UML2_MSL_PROFILES, to load additional RSM profiles.
            String path = url.getPath().substring(0, url.getPath().indexOf("profiles"));
            URI uri = URI.createURI("jar:" + path);
            //URI uri = URI.createURI("jar:file:/C:/Programs/IBM/SDP70Shared/plugins/com.ibm.xtools.uml.msl_7.5.0.v20080731_1905.jar!/");
            //URIConverter.URI_MAP.put(URI.createURI("pathmap://UML2_MSL_PROFILES/"), uri.appendSegment("profiles").appendSegment(""));
            // UML2_MSL_PROFILES are used in IBM RSM models.
            URIConverter.URI_MAP.put(URI.createURI("pathmap://UML2_MSL_PROFILES/"), uri.appendSegment("profiles").appendSegment(""));
        }
        // Add pathmap for RUP_PROFILES in com/ibm/xtools/modeler/ui/templates/7.5.500/templates-7.5.500.jar
        url = this.getClass().getResource("/profiles/RUPAnalysis.epx");
        if (url!=null)
        {
            String path = url.getPath().substring(0, url.getPath().indexOf("profiles"));
            URI uri = URI.createURI("jar:" + path);
            //URIConverter.URI_MAP.put(URI.createURI("pathmap://UML2_MSL_PROFILES/"), uri.appendSegment("profiles").appendSegment(""));
            // UML2_MSL_PROFILES are used in IBM RSM models.
            URIConverter.URI_MAP.put(URI.createURI("pathmap://RUP_PROFILES/"), uri.appendSegment("profiles").appendSegment(""));
        }

        //TODO This doesn't seem to help to resolve the pathmap.
        // moduleSearchLocations values must still be added to andromda.xml
        //TODO Enable <pathmaps><pathmap name= value=/> in andromda.xml configuration
        // pathmap://m2repository is used in starter models to reference profiles deployed in maven local repository
        String m2repository = System.getenv("M2_REPO");
        if (m2repository!=null)
        {
            URI uri = URI.createURI("file:" + m2repository.replace("\\", "/") + '/');
            // This doesn't seem to load the pathmap resources from the m2repository.
            URIConverter.URI_MAP.put(URI.createURI("pathmap://m2repository/"), uri.appendSegment(""));
            // m2repository conflicts with pathmap variable added by Sonatype eclipse plugin, use M2_REPO instead.
            URIConverter.URI_MAP.put(URI.createURI("pathmap://M2_REPO/"), uri.appendSegment(""));
        }

        // - populate the load options
        final Map<String, Object> loadOptions = this.getLoadOptions();
        // Enable notifications during load. Profiles not found do not generate a notification
        // See http://sdqweb.ipd.kit.edu/wiki/EMF_Model_Loading_Performance_Tweaks
        loadOptions.put(XMLResource.OPTION_DISABLE_NOTIFY, Boolean.FALSE);
        loadOptions.put(XMLResource.OPTION_DOM_USE_NAMESPACES_IN_SCOPE, Boolean.TRUE);
        loadOptions.put(XMLResource.OPTION_RECORD_UNKNOWN_FEATURE, Boolean.TRUE);
        loadOptions.put(XMLResource.OPTION_DEFER_ATTACHMENT, Boolean.TRUE);
        loadOptions.put(XMLResource.OPTION_DEFER_IDREF_RESOLUTION, Boolean.TRUE);
        loadOptions.put(XMLResource.OPTION_PROCESS_DANGLING_HREF_RECORD, Boolean.TRUE);
        loadOptions.put(XMLResource.OPTION_USE_DEPRECATED_METHODS, Boolean.TRUE);
        loadOptions.put(XMLResource.OPTION_USE_PARSER_POOL, new XMLParserPoolImpl());
        loadOptions.put(XMLResource.OPTION_USE_XML_NAME_TO_FEATURE_MAP, new HashMap());

        return proxyResourceSet;
    }

    /**
     * To read IBM Rational Software Modeler (RSM) files (*.emx, *.epx, ...) directly,
     * we need to register two additional metamodels for annotation elements
     * which are referenced inside the UML2 models created by IBM RSM.
     *
     * @param registry the registry in which metamodels should be registered
     * @return
     */
    private boolean registerOptionalRsmMetamodels(EPackage.Registry registry)
    {
        // RSM6 uses xtools NotationPackage, RSM7 uses gmf NotationPackage
        //boolean registered = registerOptionalMetamodel(registry, "com.ibm.xtools.notation.NotationPackage");
        boolean registered = registerOptionalMetamodel(registry, "com.ibm.xtools.umlnotation.UmlnotationPackage");
        //registered = registerOptionalMetamodel(registry, "com.ibm.xtools.topic.Topic");
        registered = registerOptionalMetamodel(registry, "org.eclipse.gmf.runtime.notation.NotationPackage");
        return registered;
    }

    /**
     * Register a metamodel in EMF so that models based on that metamodel can
     * be loaded correctly. This appears to have no effect on model processing.
     *
     * @param registry EMF package registry
     * @param ePackageClassName the class name of the package to be registered
     */
    private boolean registerOptionalMetamodel(EPackage.Registry registry, String ePackageClassName)
    {
        boolean registered = false;
        try
        {
            //Including the additional model dependencies in the project pom.xml does not make the class visible to the classloader.
            // Need to include the dependency with repository emf-uml22.
            Class ePackageClass = Class.forName(ePackageClassName);
            if (ePackageClass != null)
            {
                // get those two famous static fields
                String nsURI = (String) ePackageClass.getField("eNS_URI").get(null);
                Object eInstance = ePackageClass.getField("eINSTANCE").get(null);
                registry.put(nsURI, eInstance);
                if (logger.isDebugEnabled())
                {
                    logger.debug("Optional metamodel registered: " + nsURI);
                }
                registered = true;
            }
        }
        catch (Exception e)
        {
            // do nothing when metamodels are not present -- they are entirely optional.
        }
        return registered;
    }

    /**
     * Read all models, clear UMLUtilities model list
     * @see org.andromda.core.repository.RepositoryFacade#readModel(String[], String[])
     */
    public final void readModel(
        String[] modelUris,
        String[] moduleSearchPaths)
    {
        super.readModel(modelUris, moduleSearchPaths);
    }

    /**
     * Overridden to check that the model is of the correct type: UML2 top level Model element can also be Package.
     *
     * @see org.andromda.repositories.emf.EMFRepositoryFacade#readModel(String)
     */
    @Override
    public void readModel(final String uri)
    {
        UmlUtilities.getModels().clear();
        super.readModel(uri);
        // Just to be sure there is a valid "model" inside
        for (Resource modelResource : this.model)
        {
            //EObject modelPackage = modelResource.getEObject(modelResource.getURI().toString());
            /*Collection<EObject> modelPackages = EcoreUtil.getObjectsByType(
                modelResource.getContents(), EcorePackage.eINSTANCE.getEObject());
            // UML2 Model is a Package
            for (EObject modelPkg : modelPackages )
            {
                System.out.println("Model '" + uri + "' package " + modelPkg);
            }*/
            EObject modelPackage = (EObject) EcoreUtil.getObjectByType(
                modelResource.getContents(), EcorePackage.eINSTANCE.getEObject());
            if (modelPackage==null)
            {
                throw new RepositoryFacadeException("Model '" + uri + "' is not a valid EMF UML2 model: Model element not found");
            }
            /*else if (modelPackage instanceof org.eclipse.uml2.uml.Package)
            {
                throw new RepositoryFacadeException("Model '" + uri + "' package " + modelPackage + " is not a valid EMF UML2 model: top level Package must be a Model, not a Package.");
            }*/
            // UML2 model top level element can be either a Model or Package. Package messes up processing because metafacades assume Model is at the top.
            else if (!(modelPackage instanceof Model) && !(modelPackage instanceof org.eclipse.uml2.uml.Package))
            {
                throw new RepositoryFacadeException("Model '" + uri + "' package " + modelPackage + " is not a valid EMF UML2 model");
            }
            // Save a reference to all models in the UML22 metafacades, except for profile references
            if (!uri.contains("profile.") && !uri.contains("_Profile."))
            {
                org.eclipse.uml2.uml.Package pkg = (org.eclipse.uml2.uml.Package)modelPackage;
                if (!UmlUtilities.getModels().contains(pkg))
                {
                    UmlUtilities.getModels().add(pkg);
                }
            }
        }
    }

    /**
     * @see org.andromda.core.repository.RepositoryFacade#getModel()
     */
    public ModelAccessFacade getModel()
    {
        return this.getModel(null);
    }

    /**
     * @param uri
     * @return this.modelFacade
     * @see org.andromda.core.repository.RepositoryFacade#getModel()
     */
    public ModelAccessFacade getModel(String uri)
    {
        if (this.modelFacade == null)
        {
            try
            {
                this.modelFacade = (ModelAccessFacade) ComponentContainer.instance().newComponent(
                        UMLModelAccessFacade.class, ModelAccessFacade.class);
            }
            catch (final Throwable throwable)
            {
                throw new RepositoryFacadeException(throwable);
            }
        }
        if (StringUtils.isNotBlank(uri))
        {
            URI resource = URI.createURI(uri);
            Resource uriModel = this.resourceSet.getResource(resource, true);
            this.modelFacade.setModel(uriModel);
        }
        if (this.model != null)
        {
            this.modelFacade.setModel(this.model);
        }
        else
        {
            this.modelFacade = null;
        }
        return this.modelFacade;
    }

    /**
     * @see org.andromda.repositories.emf.EMFRepositoryFacade#close()
     */
    @Override
    public void close()
    {
        // Ignore error
    }
}