001: package org.andromda.repositories.mdr;
002:
003: import java.io.File;
004: import java.io.FileOutputStream;
005: import java.io.IOException;
006: import java.io.InputStream;
007:
008: import java.net.URL;
009:
010: import java.util.HashMap;
011: import java.util.Iterator;
012: import java.util.Map;
013:
014: import javax.jmi.model.ModelPackage;
015: import javax.jmi.model.MofPackage;
016: import javax.jmi.reflect.RefPackage;
017: import javax.jmi.xmi.MalformedXMIException;
018: import javax.jmi.xmi.XmiReader;
019:
020: import org.andromda.core.common.ClassUtils;
021: import org.andromda.core.common.ComponentContainer;
022: import org.andromda.core.common.ExceptionUtils;
023: import org.andromda.core.common.ResourceUtils;
024: import org.andromda.core.metafacade.ModelAccessFacade;
025: import org.andromda.core.repository.RepositoryFacade;
026: import org.andromda.core.repository.RepositoryFacadeException;
027: import org.apache.commons.lang.StringUtils;
028: import org.apache.log4j.Logger;
029: import org.netbeans.api.mdr.CreationFailedException;
030: import org.netbeans.api.mdr.MDRManager;
031: import org.netbeans.api.mdr.MDRepository;
032: import org.netbeans.api.xmi.XMIReaderFactory;
033: import org.netbeans.api.xmi.XMIWriter;
034: import org.netbeans.api.xmi.XMIWriterFactory;
035:
036: /**
037: * Implements an AndroMDA object model repository by using the <a href="http://mdr.netbeans.org">NetBeans
038: * MetaDataRepository </a>.
039: *
040: * @author <A HREF="httplo://www.amowers.com">Anthony Mowers </A>
041: * @author Chad Brandon
042: */
043: public class MDRepositoryFacade implements RepositoryFacade {
044: private static Logger logger = Logger
045: .getLogger(MDRepositoryFacade.class);
046: private ModelAccessFacade modelFacade = null;
047: private MDRepository repository = null;
048: protected URL metamodelUri;
049: protected RefPackage model = null;
050:
051: public MDRepositoryFacade() {
052: // configure MDR to use an in-memory storage implementation
053: System
054: .setProperty(
055: "org.netbeans.mdr.storagemodel.StorageFactoryClassName",
056: "org.netbeans.mdr.persistence.memoryimpl.StorageFactoryImpl");
057:
058: final MDRManager mdrManager = MDRManager.getDefault();
059: if (mdrManager == null) {
060: throw new RepositoryFacadeException(
061: "Could not retrieve the MDR manager");
062: }
063: this .repository = mdrManager.getDefaultRepository();
064: }
065:
066: /**
067: * Keeps track of whether or not the repository is open.
068: */
069: private boolean open = false;
070:
071: /**
072: * Opens the repository and prepares it to read in models.
073: * <p/>
074: * All the file reads are done within the context of a transaction: this seems to speed up the processing. </p>
075: *
076: * @see org.andromda.core.repository.RepositoryFacade#open()
077: */
078: public void open() {
079: if (!this .open) {
080: repository.beginTrans(true);
081: this .open = true;
082: }
083: }
084:
085: /**
086: * Closes the repository and reclaims all resources.
087: * <p/>
088: * This should only be called after all model processing has been completed. </p>
089: *
090: * @see org.andromda.core.repository.RepositoryFacade#close()
091: */
092: public void close() {
093: if (this .open) {
094: repository.endTrans(false);
095: this .clear();
096: MDRManager.getDefault().shutdownAll();
097: this .open = false;
098: }
099: }
100:
101: /**
102: * @see org.andromda.core.repository.RepositoryFacade#readModel(java.lang.String[], java.lang.String[])
103: */
104: public void readModel(String[] uris, String[] moduleSearchPath) {
105: try {
106: final MofPackage metaModel = this .loadMetaModel(this
107: .getMetamodelUri());
108: this .model = this .loadModel(uris, moduleSearchPath,
109: metaModel);
110: } catch (final Throwable throwable) {
111: throw new RepositoryFacadeException(throwable);
112: }
113: }
114:
115: /**
116: * Retrieves the URI of the metamodel.
117: *
118: * @return the metamodel uri.
119: */
120: private URL getMetamodelUri() {
121: if (this .metamodelUri == null) {
122: throw new RepositoryFacadeException(
123: "No metamodel was defined");
124: }
125: return this .metamodelUri;
126: }
127:
128: /**
129: * @see org.andromda.core.repository.RepositoryFacade#readModel(java.io.InputStream[], java.lang.String[], java.lang.String[])
130: */
131: public void readModel(final InputStream[] streams,
132: final String[] uris, final String[] moduleSearchPath) {
133: if (streams != null && uris != null
134: && uris.length != streams.length) {
135: throw new IllegalArgumentException(
136: "'streams' and 'uris' must be of the same length");
137: }
138: try {
139: final MofPackage metaModel = this .loadMetaModel(this
140: .getMetamodelUri());
141: this .model = this .loadModel(streams, uris,
142: moduleSearchPath, metaModel);
143: } catch (final Throwable throwable) {
144: throw new RepositoryFacadeException(throwable);
145: }
146: }
147:
148: /**
149: * The default XMI version if none is specified.
150: */
151: private static final String DEFAULT_XMI_VERSION = "1.2";
152:
153: /**
154: * The default encoding if none is specified
155: */
156: private static final String DEFAULT_ENCODING = "UTF-8";
157:
158: /**
159: * @see org.andromda.core.repository.RepositoryFacade#writeModel(java.lang.Object, java.lang.String,
160: * java.lang.String)
161: */
162: public void writeModel(Object model, String outputLocation,
163: String xmiVersion) {
164: this .writeModel(model, outputLocation, xmiVersion, null);
165: }
166:
167: /**
168: * Sets the location of the metamodel.
169: *
170: * @param metamodelLocation the metamodel location.
171: */
172: public void setMetamodelLocation(final String metamodelLocation) {
173: this .metamodelUri = MDRepositoryFacade.class
174: .getResource(metamodelLocation);
175: if (this .metamodelUri == null) {
176: ResourceUtils.toURL(metamodelLocation);
177: }
178: if (this .metamodelUri == null) {
179: throw new RepositoryFacadeException(
180: "No metamodel could be loaded from --> '"
181: + metamodelLocation + "'");
182: }
183: }
184:
185: /**
186: * The metamodel package name.
187: */
188: private String metamodelPackage;
189:
190: /**
191: * Sets the name of the root package of the metamodel.
192: *
193: * @param metamodelPackage the root is metamodel package name.
194: */
195: public void setMetamodelPackage(final String metamodelPackage) {
196: this .metamodelPackage = metamodelPackage;
197: }
198:
199: /**
200: * The org.netbeans.api.xmi.XMIReaderFactory implementation name.
201: */
202: private String xmiReaderFactory;
203:
204: /**
205: * Sets the org.netbeans.api.xmi.XMIReaderFactory implementation to use.
206: *
207: * @param xmiReaderFactory the fully qualified implementation class name to use.
208: */
209: public void setXmiReaderFactory(final String xmiReaderFactory) {
210: this .xmiReaderFactory = xmiReaderFactory;
211: }
212:
213: /**
214: * Stores the xmiReader instances.
215: */
216: private Map xmiReaderFactoryInstances = new HashMap();
217:
218: /**
219: * Retrieves the javax.jmi.xmi.XmiReader implementation.
220: *
221: * @return the javax.jmi.xmi.XmiReader class
222: * @throws IllegalAccessException
223: * @throws InstantiationException
224: */
225: private synchronized XMIReaderFactory getXMIReaderFactory()
226: throws InstantiationException, IllegalAccessException {
227: XMIReaderFactory factory = (org.netbeans.api.xmi.XMIReaderFactory) this .xmiReaderFactoryInstances
228: .get(this .xmiReaderFactory);
229: if (factory == null) {
230: if (this .xmiReaderFactory == null
231: || this .xmiReaderFactory.trim().length() == 0) {
232: throw new RepositoryFacadeException(
233: "No 'xmiReaderFactory' has been set");
234: }
235: Object instance = ClassUtils.loadClass(
236: this .xmiReaderFactory).newInstance();
237: if (instance instanceof XMIReaderFactory) {
238: factory = (XMIReaderFactory) instance;
239: this .xmiReaderFactoryInstances.put(
240: this .xmiReaderFactory, factory);
241: } else {
242: throw new RepositoryFacadeException(
243: "The 'xmiReaderFactory' must be an instance of '"
244: + XMIReaderFactory.class.getName()
245: + "'");
246: }
247: }
248: return factory;
249: }
250:
251: /**
252: * @see org.andromda.core.repository.RepositoryFacade#writeModel(java.lang.Object, java.lang.String,
253: * java.lang.String)
254: */
255: public void writeModel(Object model, String outputLocation,
256: String xmiVersion, String encoding) {
257: ExceptionUtils.checkNull("model", model);
258: ExceptionUtils.checkNull("outputLocation", outputLocation);
259: ExceptionUtils.checkAssignable(RefPackage.class, "model", model
260: .getClass());
261: if (StringUtils.isEmpty(xmiVersion)) {
262: xmiVersion = DEFAULT_XMI_VERSION;
263: }
264: if (StringUtils.isEmpty(encoding)) {
265: encoding = DEFAULT_ENCODING;
266: }
267: try {
268: // ensure the directory we're writing to exists
269: final File file = new File(outputLocation);
270: final File parent = file.getParentFile();
271: if (parent != null) {
272: parent.mkdirs();
273: }
274: FileOutputStream outputStream = new FileOutputStream(file);
275: final XMIWriter xmiWriter = XMIWriterFactory.getDefault()
276: .createXMIWriter();
277: xmiWriter.getConfiguration().setEncoding(encoding);
278: xmiWriter.write(outputStream, outputLocation,
279: (RefPackage) model, xmiVersion);
280: outputStream.close();
281: outputStream = null;
282: } catch (final Throwable throwable) {
283: throw new RepositoryFacadeException(throwable);
284: }
285: }
286:
287: private Class modelAccessFacade;
288:
289: /**
290: * Sets the model access facade instance to be used with this repository.
291: *
292: * @param modelAccessFacade the model access facade
293: */
294: public void setModelAccessFacade(Class modelAccessFacade) {
295: this .modelAccessFacade = modelAccessFacade;
296: }
297:
298: /**
299: * @see org.andromda.core.repository.RepositoryFacade#getModel()
300: */
301: public ModelAccessFacade getModel() {
302: if (this .modelFacade == null) {
303: try {
304: this .modelFacade = (ModelAccessFacade) ComponentContainer
305: .instance().newComponent(
306: this .modelAccessFacade,
307: ModelAccessFacade.class);
308: } catch (final Throwable throwable) {
309: throw new RepositoryFacadeException(throwable);
310: }
311: }
312: if (this .model != null) {
313: this .modelFacade.setModel(this .model);
314: } else {
315: this .modelFacade = null;
316: }
317: return this .modelFacade;
318: }
319:
320: /**
321: * Loads a metamodel into the repository.
322: *
323: * @param metamodelUri the url to the meta-model
324: * @return MofPackage for newly loaded metamodel
325: * @throws CreationFailedException
326: * @throws IOException
327: * @throws MalformedXMIException
328: * @throws IllegalAccessException
329: * @throws InstantiationException
330: */
331: private MofPackage loadMetaModel(final URL metamodelUri)
332: throws Exception {
333: long start = System.currentTimeMillis();
334: if (logger.isDebugEnabled()) {
335: logger.debug("creating MetaModel using URL --> '"
336: + metamodelUri + "'");
337: }
338:
339: // Use the metamodelUri as the name for the repository extent.
340: // This ensures we can load mutiple metamodels without them colliding.
341: ModelPackage metaModelExtent = (ModelPackage) repository
342: .getExtent(metamodelUri.toExternalForm());
343:
344: if (metaModelExtent == null) {
345: metaModelExtent = (ModelPackage) repository
346: .createExtent(metamodelUri.toExternalForm());
347: }
348:
349: MofPackage metaModelPackage = findPackage(
350: this .metamodelPackage, metaModelExtent);
351: if (metaModelPackage == null) {
352: final XmiReader xmiReader = this .getXMIReaderFactory()
353: .createXMIReader();
354: xmiReader.read(metamodelUri.toExternalForm(),
355: metaModelExtent);
356:
357: // locate the UML package definition that was just loaded in
358: metaModelPackage = findPackage(this .metamodelPackage,
359: metaModelExtent);
360: }
361:
362: if (logger.isDebugEnabled()) {
363: long duration = System.currentTimeMillis() - start;
364: logger.debug("MDRepositoryFacade: loaded metamodel in "
365: + duration + " milliseconds");
366: }
367: return metaModelPackage;
368: }
369:
370: /**
371: * @see org.andromda.core.repository.RepositoryFacade#clear()
372: */
373: public void clear() {
374: this .removeModel(EXTENT_NAME);
375: this .model = null;
376: this .modelFacade = null;
377: }
378:
379: private void removeModel(final String modelUri) {
380: // remove the model from the repository (if there is one)
381: RefPackage model = repository.getExtent(modelUri);
382: if (model != null) {
383: model.refDelete();
384: }
385: }
386:
387: /**
388: * Loads a model into the repository and validates the model against the given metaModel.
389: *
390: * @param modelUris the URIs of the model
391: * @param moduleSearchPath the paths to search for shared modules.
392: * @param metaModel meta model of model
393: * @return populated model
394: * @throws CreationFailedException unable to create model in repository
395: */
396: private RefPackage loadModel(final String[] modelUris,
397: final String[] moduleSearchPath, final MofPackage metaModel)
398: throws Exception {
399: if (logger.isDebugEnabled()) {
400: logger.debug("loadModel: starting to load model from '"
401: + modelUris[0] + "'");
402: }
403: long start = System.currentTimeMillis();
404:
405: RefPackage model = null;
406: if (modelUris != null) {
407: model = this .createModel(metaModel);
408: final XmiReader xmiReader = this .getXMIReaderFactory()
409: .createXMIReader(
410: new MDRXmiReferenceResolver(
411: new RefPackage[] { model },
412: moduleSearchPath));
413: try {
414: final int uriNumber = modelUris.length;
415: for (int ctr = 0; ctr < uriNumber; ctr++) {
416: final String uri = modelUris[ctr];
417: if (uri != null) {
418: xmiReader.read(modelUris[ctr], model);
419: }
420: }
421: } catch (final Throwable throwable) {
422: throw new RepositoryFacadeException(throwable);
423: }
424: if (logger.isDebugEnabled()) {
425: logger.debug("read URIs and created model");
426: }
427: }
428:
429: if (logger.isDebugEnabled()) {
430: long duration = System.currentTimeMillis() - start;
431: logger.debug("loadModel: finished loading model in "
432: + duration + " milliseconds.");
433: }
434:
435: return model;
436: }
437:
438: /**
439: * Loads a model into the repository and validates the model against the given metaModel.
440: *
441: * @param modelStreams input streams containing the models.
442: * @param uris the URIs of the models.
443: * @param moduleSearchPaths the paths to search for shared modules.
444: * @param metaModel meta model of model
445: * @return populated model
446: * @throws CreationFailedException unable to create model in repository
447: */
448: private RefPackage loadModel(final InputStream[] modelStreams,
449: final String[] uris, final String[] moduleSearchPaths,
450: final MofPackage metaModel) throws Exception {
451: final RefPackage model = this .createModel(metaModel);
452: if (modelStreams != null) {
453: final XmiReader xmiReader = this .getXMIReaderFactory()
454: .createXMIReader(
455: new MDRXmiReferenceResolver(
456: new RefPackage[] { model },
457: moduleSearchPaths));
458: try {
459: final int streamNumber = modelStreams.length;
460: for (int ctr = 0; ctr < streamNumber; ctr++) {
461: final InputStream stream = modelStreams[ctr];
462: String uri = null;
463: if (uris != null) {
464: uri = uris[ctr];
465: }
466: if (stream != null) {
467: xmiReader.read(stream, uri, model);
468: }
469: }
470: } catch (final Throwable throwable) {
471: throw new RepositoryFacadeException(throwable);
472: }
473: if (logger.isDebugEnabled()) {
474: logger.debug("read URIs and created model");
475: }
476: }
477: return model;
478: }
479:
480: /**
481: * The name of the extent under which all models loaded into the repository
482: * are stored (makes up one big model).
483: */
484: private static final String EXTENT_NAME = "model";
485:
486: /**
487: * Constructs the model from the given <code>metaModel</code>.
488: *
489: * @param metaModel the meta model.
490: * @return the package.
491: * @throws CreationFailedException
492: */
493: private RefPackage createModel(final MofPackage metaModel)
494: throws CreationFailedException {
495: RefPackage model = this .repository.getExtent(EXTENT_NAME);
496: if (model != null) {
497: this .removeModel(EXTENT_NAME);
498: }
499: if (logger.isDebugEnabled()) {
500: logger.debug("creating the new meta model");
501: }
502: model = repository.createExtent(EXTENT_NAME, metaModel);
503: if (logger.isDebugEnabled()) {
504: logger.debug("created model extent");
505: }
506: return model;
507: }
508:
509: /**
510: * Searches a meta model for the specified package.
511: *
512: * @param packageName name of package for which to search
513: * @param metaModel meta model to search
514: * @return MofPackage
515: */
516: private MofPackage findPackage(final String packageName,
517: final ModelPackage metaModel) {
518: MofPackage mofPackage = null;
519: for (final Iterator iterator = metaModel.getMofPackage()
520: .refAllOfClass().iterator(); iterator.hasNext();) {
521: final javax.jmi.model.ModelElement element = (javax.jmi.model.ModelElement) iterator
522: .next();
523: if (element.getName().equals(packageName)) {
524: mofPackage = (MofPackage) element;
525: break;
526: }
527: }
528: return mofPackage;
529: }
530: }
|