001package org.andromda.repositories.emf;
002
003import java.io.IOException;
004import java.io.InputStream;
005import java.net.URL;
006import java.util.ArrayList;
007import java.util.Arrays;
008import java.util.HashMap;
009import java.util.Iterator;
010import java.util.List;
011import java.util.Map;
012import org.andromda.core.common.ResourceFinder;
013import org.andromda.core.engine.ModelProcessor;
014import org.andromda.core.metafacade.ModelAccessFacade;
015import org.andromda.core.repository.RepositoryFacade;
016import org.andromda.core.repository.RepositoryFacadeException;
017import org.apache.log4j.Logger;
018import org.eclipse.emf.common.util.EList;
019import org.eclipse.emf.common.util.URI;
020import org.eclipse.emf.ecore.EObject;
021import org.eclipse.emf.ecore.resource.Resource;
022import org.eclipse.emf.ecore.resource.ResourceSet;
023import org.eclipse.emf.ecore.resource.Resource.Diagnostic;
024
025/**
026 * An abstract EMF {@link RepositoryFacade} instance that should be extended by any repository wishing to load EMF models.
027 *
028 * @author Steve Jerman
029 * @author Chad Brandon
030 * @author Bob Fields (Multiple model support)
031 */
032public abstract class EMFRepositoryFacade
033    implements RepositoryFacade
034{
035    /**
036     * The logger instance.
037     */
038    private static final Logger logger = Logger.getLogger(EMFRepositoryFacade.class);
039
040    /**
041     * Stores the resources (i.e. models) loaded into EMF.
042     */
043    protected ResourceSet resourceSet;
044
045    /**
046     *
047     */
048    protected ModelAccessFacade modelFacade;
049
050    /**
051     * Stores the actual loaded model.
052     */
053    // Modification required for multi-model support - fix compile errors after change
054    // and implement multiple model processing by iterating through models in the list.
055    protected List<Resource> model;
056
057    /**
058     * The options for loading the model.
059     */
060    private Map<String, Object> loadOptions = new HashMap<String, Object>();
061
062    /**
063     * Gets the current load options.
064     *
065     * @return the load options.
066     */
067    protected Map<String, Object> getLoadOptions()
068    {
069        return this.loadOptions;
070    }
071
072    /**
073     * Reads the model with the given <code>uri</code>.
074     *
075     * @param uri the URI to the model
076     */
077    public void readModel(final String uri)
078    {
079        try
080        {
081            if (this.model==null)
082            {
083                this.model = new ArrayList<Resource>();
084            }
085            Resource modelResource = this.resourceSet.createResource(EMFRepositoryFacadeUtils.createUri(uri));
086            if (modelResource == null)
087            {
088                throw new RepositoryFacadeException('\'' + uri + "' is an invalid model");
089            }
090            modelResource.load(this.getLoadOptions());
091            // Show errors and warnings, if any....
092            EList<Diagnostic> errors = modelResource.getErrors();
093            if (errors!=null && !errors.isEmpty())
094            {
095                logger.info(errors);
096            }
097            EList<Diagnostic> warnings = modelResource.getWarnings();
098            if (warnings!=null && !warnings.isEmpty())
099            {
100                logger.info(warnings);
101            }
102            // Don't validate that model resources can be loaded, if not necessary. Speeds up processing.
103            if (ModelProcessor.getModelValidation())
104            {
105                try {
106                    //logger.debug("EMFRepositoryFacade.readModel.resolve: " + modelResource.getURI());
107                    // Duplicate call to EcoreUtil.resolveAll(modelResource);
108                    //long now = System.currentTimeMillis();
109                    //for (EObject eObject : modelResource.getAllContents())
110                    for (Iterator<EObject> i = modelResource.getAllContents();  i.hasNext(); )
111                    {
112                        //long now1 = System.currentTimeMillis();
113                        EObject eObject = i.next();
114                        //logger.debug("EMFRepositoryFacade.resolveAll.crossRef: " + EcoreUtil.getURI(eObject) + " " + (System.currentTimeMillis()-now1) + " ms");
115                        for (Iterator<EObject> crossRefIterator =  eObject.eCrossReferences().iterator();  crossRefIterator.hasNext(); )
116                        {
117                            try
118                            {
119                                //long now2 = System.currentTimeMillis();
120                                //EObject crossRef =
121                                    crossRefIterator.next();
122                                //EObject resolved = EcoreUtil.resolve(crossRef, this.resourceSet);
123                                //logger.debug("EMFRepositoryFacade.resolveAll.crossRef: " + crossRef.toString() + " = " + EcoreUtil.getURI(crossRef) + " " + (System.currentTimeMillis()-now2) + " ms");
124                            }
125                            catch (Exception ex)
126                            {
127                                logger.error("EMFRepositoryFacade.readModel.resolveAll on " + eObject + ": " + ex);
128                            }
129                        }
130                    }
131                } catch (RuntimeException e) {
132                    logger.error("EMFRepositoryFacade.readModel.resolveAll :" + e);
133                }
134            }
135            this.model.add(modelResource);
136        }
137        catch (final Exception exception)
138        {
139            throw new RepositoryFacadeException(exception);
140        }
141    }
142
143    /**
144     * @see org.andromda.core.repository.RepositoryFacade#open()
145     */
146    public void open()
147    {
148        this.resourceSet = this.createNewResourceSet();
149    }
150
151    /**
152     * Creates and returns a new resource suitable for loading models into EMF.
153     * This callback is used when (re-)initializing this repository so that it can be reused between different
154     * AndroMDA runs, once a resource set is used for a model it becomes 'polluted' so that subsequent models
155     * will see things from the previous runs, which might mess up the processing.
156     *
157     * @return a new resource set to be used by this repository
158     */
159    public abstract ResourceSet createNewResourceSet();
160
161    /**
162     * Ignore. Avoid compiler warning.
163     * @see org.andromda.core.repository.RepositoryFacade#close()
164     */
165    public abstract void close();
166
167    /**
168     * The path to any modules found on the classpath.
169     */
170    private static final String MODULES_PATH = "META-INF/emf/modules";
171
172    /**
173     * @see org.andromda.core.repository.RepositoryFacade#readModel(String[], String[])
174     */
175    public void readModel(
176        String[] modelUris,
177        String[] moduleSearchPaths)
178    {
179        if (modelUris == null || modelUris.length == 0)
180        {
181            throw new RepositoryFacadeException("No model specified.");
182        }
183        final List<String> moduleSearchPathList = new ArrayList<String>();
184        if (moduleSearchPaths != null)
185        {
186            moduleSearchPathList.addAll(Arrays.asList(moduleSearchPaths));
187        }
188
189        // - first add the default module search paths maps that are found on the classpath
190        URL[] classpathSearchPaths = ResourceFinder.findResources(MODULES_PATH);
191        if (classpathSearchPaths != null)
192        {
193            classpathSearchPaths = ResourceFinder.findResources("profiles");
194        }
195        if (classpathSearchPaths != null)
196        {
197            final int numberOfClasspathSearchPaths = classpathSearchPaths.length;
198            for (int ctr = 0; ctr < numberOfClasspathSearchPaths; ctr++)
199            {
200                final URL classpathSearchPath = classpathSearchPaths[ctr];
201                if (classpathSearchPath != null)
202                {
203                    moduleSearchPathList.add(classpathSearchPath.toString());
204                }
205            }
206        }
207        logger.debug("ModuleSearchPaths: " + moduleSearchPathList);
208        // Add the new URI map to the existing URI map
209
210        this.resourceSet.setURIConverter(new EMFURIConverter(moduleSearchPathList,
211            this.resourceSet.getURIConverter().getURIMap()));
212        if (modelUris.length > 0)
213        {
214            final int numberOfModelUris = modelUris.length;
215            for (int ctr = 0; ctr < numberOfModelUris; ctr++)
216            {
217                this.readModel(modelUris[ctr]);
218            }
219        }
220    }
221
222    /**
223     * @see org.andromda.core.repository.RepositoryFacade#readModel(java.io.InputStream[], String[], String[])
224     */
225    public void readModel(
226        InputStream[] stream,
227        String[] modelUri,
228        String[] moduleSearchPaths)
229    {
230        this.readModel(
231            modelUri,
232            moduleSearchPaths);
233    }
234
235    /**
236     * @see org.andromda.core.repository.RepositoryFacade#writeModel(Object, String, String, String)
237     */
238    public void writeModel(
239        Object modelIn,
240        String location,
241        String version,
242        String encoding)
243    {
244        this.writeModel(
245            modelIn,
246            location,
247            "");
248    }
249
250    /**
251     * @see org.andromda.core.repository.RepositoryFacade#writeModel(Object, String, String)
252     */
253    public void writeModel(
254        Object modelIn,
255        String location,
256        String version)
257    {
258        final org.eclipse.emf.ecore.EModelElement element = (org.eclipse.emf.ecore.EModelElement)modelIn;
259        final Resource resource = element.eResource();
260        final URI uri = URI.createURI(location);
261        resource.setURI(uri);
262        try
263        {
264            resource.save(null);
265        }
266        catch (IOException exception)
267        {
268            throw new RepositoryFacadeException("Could not save model", exception);
269        }
270    }
271
272    /**
273     * @see org.andromda.core.repository.RepositoryFacade#clear()
274     */
275    public void clear()
276    {
277        this.model = null;
278        this.resourceSet = this.createNewResourceSet();
279    }
280}