View Javadoc
1   package org.andromda.repositories.mdr;
2   
3   import java.io.File;
4   import java.io.FileOutputStream;
5   import java.io.IOException;
6   import java.io.InputStream;
7   import java.net.URL;
8   import java.util.HashMap;
9   import java.util.Iterator;
10  import java.util.Map;
11  import javax.jmi.model.ModelPackage;
12  import javax.jmi.model.MofPackage;
13  import javax.jmi.reflect.RefPackage;
14  import javax.jmi.xmi.MalformedXMIException;
15  import javax.jmi.xmi.XmiReader;
16  import org.andromda.core.common.ClassUtils;
17  import org.andromda.core.common.ComponentContainer;
18  import org.andromda.core.common.ExceptionUtils;
19  import org.andromda.core.common.ResourceUtils;
20  import org.andromda.core.metafacade.ModelAccessFacade;
21  import org.andromda.core.repository.RepositoryFacade;
22  import org.andromda.core.repository.RepositoryFacadeException;
23  import org.apache.commons.lang.StringUtils;
24  import org.apache.log4j.Logger;
25  import org.netbeans.api.mdr.CreationFailedException;
26  import org.netbeans.api.mdr.MDRManager;
27  import org.netbeans.api.mdr.MDRepository;
28  import org.netbeans.api.xmi.XMIReaderFactory;
29  import org.netbeans.api.xmi.XMIWriter;
30  import org.netbeans.api.xmi.XMIWriterFactory;
31  
32  
33  /**
34   * Implements an AndroMDA object model repository by using the <a href="http://mdr.netbeans.org">NetBeans
35   * MetaDataRepository </a>.
36   *
37   * @author <A HREF="httplo://www.amowers.com">Anthony Mowers </A>
38   * @author Chad Brandon
39   */
40  public class MDRepositoryFacade
41      implements RepositoryFacade
42  {
43      private static final Logger logger = Logger.getLogger(MDRepositoryFacade.class);
44      private ModelAccessFacade modelFacade = null;
45      private MDRepository repository = null;
46      /**
47       *
48       */
49      protected URL metamodelUri;
50      /**
51       *
52       */
53      protected RefPackage model = null;
54  
55      /**
56       *
57       */
58      public MDRepositoryFacade()
59      {
60          // configure MDR to use an in-memory storage implementation
61          System.setProperty(
62              "org.netbeans.mdr.storagemodel.StorageFactoryClassName",
63              "org.netbeans.mdr.persistence.memoryimpl.StorageFactoryImpl");
64  
65          final MDRManager mdrManager = MDRManager.getDefault();
66          if (mdrManager == null)
67          {
68              throw new RepositoryFacadeException("Could not retrieve the MDR manager");
69          }
70          this.repository = mdrManager.getDefaultRepository();
71      }
72  
73      /**
74       * Keeps track of whether or not the repository is open.
75       */
76      private boolean open = false;
77  
78      /**
79       * Opens the repository and prepares it to read in models.
80       * <p/>
81       * All the file reads are done within the context of a transaction: this seems to speed up the processing. </p>
82       *
83       * @see org.andromda.core.repository.RepositoryFacade#open()
84       */
85      public void open()
86      {
87          if (!this.open)
88          {
89              repository.beginTrans(true);
90              this.open = true;
91          }
92      }
93  
94      /**
95       * Closes the repository and reclaims all resources.
96       * <p/>
97       * This should only be called after all model processing has been completed. </p>
98       *
99       * @see org.andromda.core.repository.RepositoryFacade#close()
100      */
101     public void close()
102     {
103         if (this.open)
104         {
105             repository.endTrans(false);
106             this.clear();
107             MDRManager.getDefault().shutdownAll();
108             this.open = false;
109         }
110     }
111 
112     /**
113      * @see org.andromda.core.repository.RepositoryFacade#readModel(String[], String[])
114      */
115     public void readModel(
116         String[] uris,
117         String[] moduleSearchPath)
118     {
119         try
120         {
121             final MofPackage metaModel = this.loadMetaModel(this.getMetamodelUri());
122             this.model = this.loadModel(
123                     uris,
124                     moduleSearchPath,
125                     metaModel);
126         }
127         catch (final Throwable throwable)
128         {
129             throw new RepositoryFacadeException(throwable);
130         }
131     }
132 
133     /**
134      * Retrieves the URI of the metamodel.
135      *
136      * @return the metamodel uri.
137      */
138     private URL getMetamodelUri()
139     {
140         if (this.metamodelUri == null)
141         {
142             throw new RepositoryFacadeException("No metamodel was defined");
143         }
144         return this.metamodelUri;
145     }
146 
147     /**
148      * @see org.andromda.core.repository.RepositoryFacade#readModel(java.io.InputStream[], String[], String[])
149      */
150     public void readModel(
151         final InputStream[] streams,
152         final String[] uris,
153         final String[] moduleSearchPath)
154     {
155         if (streams != null && uris != null && uris.length != streams.length)
156         {
157             throw new IllegalArgumentException("'streams' and 'uris' must be of the same length");
158         }
159         try
160         {
161             final MofPackage metaModel = this.loadMetaModel(this.getMetamodelUri());
162             this.model = this.loadModel(
163                     streams,
164                     uris,
165                     moduleSearchPath,
166                     metaModel);
167         }
168         catch (final Throwable throwable)
169         {
170             throw new RepositoryFacadeException(throwable);
171         }
172     }
173 
174     /**
175      * The default XMI version if none is specified.
176      */
177     private static final String DEFAULT_XMI_VERSION = "1.2";
178 
179     /**
180      * The default encoding if none is specified
181      */
182     private static final String DEFAULT_ENCODING = "UTF-8";
183 
184     /**
185      * @see org.andromda.core.repository.RepositoryFacade#writeModel(Object, String,
186      *      String)
187      */
188     public void writeModel(
189         Object model,
190         String outputLocation,
191         String xmiVersion)
192     {
193         this.writeModel(
194             model,
195             outputLocation,
196             xmiVersion,
197             null);
198     }
199 
200     /**
201      * Sets the location of the metamodel.
202      *
203      * @param metamodelLocation the metamodel location.
204      */
205     public void setMetamodelLocation(final String metamodelLocation)
206     {
207         this.metamodelUri = MDRepositoryFacade.class.getResource(metamodelLocation);
208         if (this.metamodelUri == null)
209         {
210             ResourceUtils.toURL(metamodelLocation);
211         }
212         if (this.metamodelUri == null)
213         {
214             throw new RepositoryFacadeException("No metamodel could be loaded from --> '" + metamodelLocation + '\'');
215         }
216     }
217 
218     /**
219      * The metamodel package name.
220      */
221     private String metamodelPackage;
222 
223     /**
224      * Sets the name of the root package of the metamodel.
225      *
226      * @param metamodelPackage the root is metamodel package name.
227      */
228     public void setMetamodelPackage(final String metamodelPackage)
229     {
230         this.metamodelPackage = metamodelPackage;
231     }
232 
233     /**
234      * The org.netbeans.api.xmi.XMIReaderFactory implementation name.
235      */
236     private String xmiReaderFactory;
237 
238     /**
239      * Sets the org.netbeans.api.xmi.XMIReaderFactory implementation to use.
240      *
241      * @param xmiReaderFactory the fully qualified implementation class name to use.
242      */
243     public void setXmiReaderFactory(final String xmiReaderFactory)
244     {
245         this.xmiReaderFactory = xmiReaderFactory;
246     }
247 
248     /**
249      * Stores the xmiReader instances.
250      */
251     private Map<String, XMIReaderFactory> xmiReaderFactoryInstances = new HashMap<String, XMIReaderFactory>();
252 
253     /**
254      * Retrieves the javax.jmi.xmi.XmiReader implementation.
255      *
256      * @return the javax.jmi.xmi.XmiReader class
257      * @throws IllegalAccessException
258      * @throws InstantiationException
259      */
260     private synchronized XMIReaderFactory getXMIReaderFactory()
261         throws InstantiationException, IllegalAccessException
262     {
263         XMIReaderFactory factory =
264             this.xmiReaderFactoryInstances.get(this.xmiReaderFactory);
265         if (factory == null)
266         {
267             if (StringUtils.isBlank(this.xmiReaderFactory))
268             {
269                 throw new RepositoryFacadeException("No 'xmiReaderFactory' has been set");
270             }
271             Object instance = ClassUtils.loadClass(this.xmiReaderFactory).newInstance();
272             if (instance instanceof XMIReaderFactory)
273             {
274                 factory = (XMIReaderFactory)instance;
275                 this.xmiReaderFactoryInstances.put(
276                     this.xmiReaderFactory,
277                     factory);
278             }
279             else
280             {
281                 throw new RepositoryFacadeException("The 'xmiReaderFactory' must be an instance of '" +
282                     XMIReaderFactory.class.getName() + '\'');
283             }
284         }
285         return factory;
286     }
287 
288     /**
289      * @param model
290      * @param outputLocation
291      * @param xmiVersion
292      * @param encoding
293      * @see org.andromda.core.repository.RepositoryFacade#writeModel(Object, String,
294      *      String)
295      */
296     public void writeModel(
297         Object model,
298         String outputLocation,
299         String xmiVersion,
300         String encoding)
301     {
302         ExceptionUtils.checkNull(
303             "model",
304             model);
305         ExceptionUtils.checkNull(
306             "outputLocation",
307             outputLocation);
308         ExceptionUtils.checkAssignable(
309             RefPackage.class,
310             "model",
311             model.getClass());
312         if (StringUtils.isEmpty(xmiVersion))
313         {
314             xmiVersion = DEFAULT_XMI_VERSION;
315         }
316         if (StringUtils.isEmpty(encoding))
317         {
318             encoding = DEFAULT_ENCODING;
319         }
320         try
321         {
322             // ensure the directory we're writing to exists
323             final File file = new File(outputLocation);
324             final File parent = file.getParentFile();
325             if (parent != null)
326             {
327                 parent.mkdirs();
328             }
329             FileOutputStream outputStream = new FileOutputStream(file);
330             final XMIWriter xmiWriter = XMIWriterFactory.getDefault().createXMIWriter();
331             xmiWriter.getConfiguration().setEncoding(encoding);
332             xmiWriter.write(
333                 outputStream,
334                 outputLocation,
335                 (RefPackage)model,
336                 xmiVersion);
337             outputStream.close();
338             outputStream = null;
339         }
340         catch (final Throwable throwable)
341         {
342             throw new RepositoryFacadeException(throwable);
343         }
344     }
345 
346     private Class modelAccessFacade;
347 
348     /**
349      * Sets the model access facade instance to be used with this repository.
350      *
351      * @param modelAccessFacade the model access facade
352      */
353     public void setModelAccessFacade(Class modelAccessFacade)
354     {
355         this.modelAccessFacade = modelAccessFacade;
356     }
357 
358     /**
359      * @see org.andromda.core.repository.RepositoryFacade#getModel()
360      */
361     public ModelAccessFacade getModel()
362     {
363         if (this.modelFacade == null)
364         {
365             try
366             {
367                 this.modelFacade =
368                     (ModelAccessFacade)ComponentContainer.instance().newComponent(
369                         this.modelAccessFacade,
370                         ModelAccessFacade.class);
371             }
372             catch (final Throwable throwable)
373             {
374                 throw new RepositoryFacadeException(throwable);
375             }
376         }
377         if (this.model != null)
378         {
379             this.modelFacade.setModel(this.model);
380         }
381         else
382         {
383             this.modelFacade = null;
384         }
385         return this.modelFacade;
386     }
387 
388     /**
389      * Loads a metamodel into the repository.
390      *
391      * @param metamodelUri the url to the meta-model
392      * @return MofPackage for newly loaded metamodel
393      * @throws CreationFailedException
394      * @throws IOException
395      * @throws MalformedXMIException
396      * @throws IllegalAccessException
397      * @throws InstantiationException
398      */
399     private MofPackage loadMetaModel(final URL metamodelUri)
400         throws Exception
401     {
402         long start = System.currentTimeMillis();
403         if (logger.isDebugEnabled())
404         {
405             logger.debug("creating MetaModel using URL --> '" + metamodelUri + '\'');
406         }
407 
408         // Use the metamodelUri as the name for the repository extent.
409         // This ensures we can load multiple metamodels without them colliding.
410         ModelPackage metaModelExtent = (ModelPackage)repository.getExtent(metamodelUri.toExternalForm());
411 
412         if (metaModelExtent == null)
413         {
414             metaModelExtent = (ModelPackage)repository.createExtent(metamodelUri.toExternalForm());
415         }
416 
417         MofPackage metaModelPackage = findPackage(
418                 this.metamodelPackage,
419                 metaModelExtent);
420         if (metaModelPackage == null)
421         {
422             final XmiReader xmiReader = this.getXMIReaderFactory().createXMIReader();
423             xmiReader.read(
424                 metamodelUri.toExternalForm(),
425                 metaModelExtent);
426 
427             // locate the UML package definition that was just loaded in
428             metaModelPackage = findPackage(
429                     this.metamodelPackage,
430                     metaModelExtent);
431         }
432 
433         if (logger.isDebugEnabled())
434         {
435             long duration = System.currentTimeMillis() - start;
436             logger.debug("MDRepositoryFacade: loaded metamodel in " + duration + " milliseconds");
437         }
438         return metaModelPackage;
439     }
440 
441     /**
442      * @see org.andromda.core.repository.RepositoryFacade#clear()
443      */
444     public void clear()
445     {
446         this.removeModel(EXTENT_NAME);
447         this.model = null;
448         this.modelFacade = null;
449     }
450 
451     private void removeModel(final String modelUri)
452     {
453         // remove the model from the repository (if there is one)
454         RefPackage model = repository.getExtent(modelUri);
455         if (model != null)
456         {
457             model.refDelete();
458         }
459     }
460 
461     /**
462      * Loads a model into the repository and validates the model against the given metaModel.
463      *
464      * @param modelUris the URIs of the model
465      * @param moduleSearchPath the paths to search for shared modules.
466      * @param metaModel meta model of model
467      * @return populated model
468      * @throws CreationFailedException unable to create model in repository
469      */
470     private RefPackage loadModel(
471         final String[] modelUris,
472         final String[] moduleSearchPath,
473         final MofPackage metaModel)
474         throws Exception
475     {
476         if (logger.isDebugEnabled())
477         {
478             logger.debug("loadModel: starting to load model from '" + modelUris[0] + '\'');
479         }
480         long start = System.currentTimeMillis();
481 
482         RefPackage model = null;
483         if (modelUris != null)
484         {
485             model = this.createModel(metaModel);
486             final XmiReader xmiReader =
487                 this.getXMIReaderFactory().createXMIReader(
488                     new MDRXmiReferenceResolver(
489                         new RefPackage[] {model},
490                         moduleSearchPath));
491             try
492             {
493                 final int uriNumber = modelUris.length;
494                 for (int ctr = 0; ctr < uriNumber; ctr++)
495                 {
496                     final String uri = modelUris[ctr];
497                     if (uri != null)
498                     {
499                         xmiReader.read(
500                             modelUris[ctr],
501                             model);
502                     }
503                 }
504             }
505             catch (final Throwable throwable)
506             {
507                 throw new RepositoryFacadeException(throwable);
508             }
509             if (logger.isDebugEnabled())
510             {
511                 logger.debug("read URIs and created model");
512             }
513         }
514 
515         if (logger.isDebugEnabled())
516         {
517             long duration = System.currentTimeMillis() - start;
518             logger.debug("loadModel: finished loading model in " + duration + " milliseconds.");
519         }
520 
521         return model;
522     }
523 
524     /**
525      * Loads a model into the repository and validates the model against the given metaModel.
526      *
527      * @param modelStreams input streams containing the models.
528      * @param uris the URIs of the models.
529      * @param moduleSearchPaths the paths to search for shared modules.
530      * @param metaModel meta model of model
531      * @return populated model
532      * @throws CreationFailedException unable to create model in repository
533      */
534     private RefPackage loadModel(
535         final InputStream[] modelStreams,
536         final String[] uris,
537         final String[] moduleSearchPaths,
538         final MofPackage metaModel)
539         throws Exception
540     {
541         final RefPackage model = this.createModel(metaModel);
542         if (modelStreams != null)
543         {
544             final XmiReader xmiReader =
545                 this.getXMIReaderFactory().createXMIReader(
546                     new MDRXmiReferenceResolver(
547                         new RefPackage[] {model},
548                         moduleSearchPaths));
549             try
550             {
551                 final int streamNumber = modelStreams.length;
552                 for (int ctr = 0; ctr < streamNumber; ctr++)
553                 {
554                     final InputStream stream = modelStreams[ctr];
555                     String uri = null;
556                     if (uris != null)
557                     {
558                         uri = uris[ctr];
559                     }
560                     if (stream != null)
561                     {
562                         xmiReader.read(
563                             stream,
564                             uri,
565                             model);
566                     }
567                 }
568             }
569             catch (final Throwable throwable)
570             {
571                 throw new RepositoryFacadeException(throwable);
572             }
573             if (logger.isDebugEnabled())
574             {
575                 logger.debug("read URIs and created model");
576             }
577         }
578         return model;
579     }
580 
581     /**
582      * The name of the extent under which all models loaded into the repository
583      * are stored (makes up one big model).
584      */
585     private static final String EXTENT_NAME = "model";
586 
587     /**
588      * Constructs the model from the given <code>metaModel</code>.
589      *
590      * @param metaModel the meta model.
591      * @return the package.
592      * @throws CreationFailedException
593      */
594     private RefPackage createModel(final MofPackage metaModel)
595         throws CreationFailedException
596     {
597         RefPackage model = this.repository.getExtent(EXTENT_NAME);
598         if (model != null)
599         {
600             this.removeModel(EXTENT_NAME);
601         }
602         if (logger.isDebugEnabled())
603         {
604             logger.debug("creating the new meta model");
605         }
606         model = repository.createExtent(
607                 EXTENT_NAME,
608                 metaModel);
609         if (logger.isDebugEnabled())
610         {
611             logger.debug("created model extent");
612         }
613         return model;
614     }
615 
616     /**
617      * Searches a meta model for the specified package.
618      *
619      * @param packageName name of package for which to search
620      * @param metaModel   meta model to search
621      * @return MofPackage
622      */
623     private MofPackage findPackage(
624         final String packageName,
625         final ModelPackage metaModel)
626     {
627         MofPackage mofPackage = null;
628         for (final Iterator iterator = metaModel.getMofPackage().refAllOfClass().iterator(); iterator.hasNext();)
629         {
630             final javax.jmi.model.ModelElement element = (javax.jmi.model.ModelElement)iterator.next();
631             if (element.getName().equals(packageName))
632             {
633                 mofPackage = (MofPackage)element;
634                 break;
635             }
636         }
637         return mofPackage;
638     }
639 }