001/*
002 * Copyright (c) 2004-2010 The Regents of the University of California.
003 * All rights reserved.
004 *
005 * '$Author: welker $'
006 * '$Date: 2010-05-06 05:21:26 +0000 (Thu, 06 May 2010) $' 
007 * '$Revision: 24234 $'
008 * 
009 * Permission is hereby granted, without written agreement and without
010 * license or royalty fees, to use, copy, modify, and distribute this
011 * software and its documentation for any purpose, provided that the above
012 * copyright notice and the following two paragraphs appear in all copies
013 * of this software.
014 *
015 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
016 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
017 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
018 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
019 * SUCH DAMAGE.
020 *
021 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
022 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
023 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
024 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
025 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
026 * ENHANCEMENTS, OR MODIFICATIONS.
027 *
028 */
029
030package org.ecoinformatics.seek.sms;
031
032/**
033 *@author    Shawn Bowers
034 *
035 *  A quick hack at a start to a local Kepler LSID suite of services
036 *  for kepler.  This ultimately is an implementation of each
037 *  LSIDxyzService, but is local to Kepler. Not sure how all of this
038 *  will eventually work ...
039 *
040 *  This class locally maintains the default actor library. The actor
041 *  library is passed at start up; and a hash table is created of all
042 *  named objects in the library.  New objects can be added, etc.
043 *  Each new addition or change should update default actor library,
044 *  persisting the change locally.
045 */
046
047import java.io.BufferedWriter;
048import java.io.File;
049import java.io.FileWriter;
050import java.util.Enumeration;
051import java.util.Hashtable;
052import java.util.Iterator;
053import java.util.List;
054import java.util.StringTokenizer;
055import java.util.Vector;
056
057import org.kepler.moml.NamedObjId;
058
059import ptolemy.actor.lib.Const;
060import ptolemy.kernel.ComponentEntity;
061import ptolemy.kernel.CompositeEntity;
062import ptolemy.kernel.util.Attribute;
063import ptolemy.kernel.util.NamedObj;
064import ptolemy.moml.EntityLibrary;
065import ptolemy.vergil.tree.EntityTreeModel;
066import ptolemy.vergil.tree.VisibleTreeModel;
067
068/*
069 *  KeplerLocalLSIDService Interface
070 *  {
071 *  // singleton
072 *
073 *  KeplerLocalLSIDService instance();
074 *
075 *  // initialization hacks, called from TabbedLibraryPane at setup
076 *
077 *  void setBasicActorLibrary(EntityTreeModel _libraryModel);
078 *  EntityTreeModel getBasicActorLibrary();
079 *
080 *  // lsid ops
081 *
082 *  void assignLSID(String lsid, NamedObj obj);
083 *  void updateLSID(String lsid, NamedObj obj);
084 *  NamedObj getData(String lsid);
085 *  Iterator<String> getLSIDFor(NamedObj obj)
086 *
087 *  // helper ops
088 *
089 *  String createLSID(String namespace, String id);
090 *  String createLocallyUniqueLSID(String namespace);
091 *  boolean isAssignedLSID(String lsid);
092 *  boolean isWellFormedLSID(String lsid)
093 *  Iterator<String> assignedLSIDs();
094 *  void commitChanges()
095 *  }
096 */
097
098/**
099 * Description of the Class
100 * 
101 *@author berkley
102 *@created February 17, 2005
103 */
104public class KeplerLocalLSIDService {
105
106        /*
107         * PRIVATE ATTRIBUTES
108         */
109        // singleton instance
110        private static KeplerLocalLSIDService _service = null;
111
112        // indexed, main-memory storage and management
113        private Hashtable _assignedObjects = new Hashtable();
114        // maps lsid -> namedObj
115        private EntityTreeModel _libraryModel = null;
116        // orig. lib. of actors
117        private Vector _managedLsids = new Vector();
118        // list of managed lsids; that have been assigned
119        private Hashtable _lastDomainId = new Hashtable();
120        // for finding new, unqiue domains
121
122        // references to the database of actors
123        private static String KEPLER = System.getProperty("KEPLER");
124        private static String KEPLERACTORLIB = KEPLER
125                        + "/configs/ptolemy/configs/kepler/basicKeplerActorLibrary.xml";
126
127        // prefix for identifier
128        private static String URN = "urn";
129        private static String LSID = "lsid";
130        private static String DOMAIN = "localhost";
131
132        /*
133         * PROTECTED CONSTRUCTOR
134         */
135        /** Constructor for the KeplerLocalLSIDService object */
136        protected KeplerLocalLSIDService() {
137                // construct a default library, prior to setBasicActorLibrary
138                _libraryModel = new VisibleTreeModel(new CompositeEntity());
139        }
140
141        /*
142         * SINGLETON CONSTRUCTOR
143         */
144
145        /**
146         *@return The unique instance of this class This must be called to
147         *         create/obtain an instance of the service
148         */
149        public static KeplerLocalLSIDService instance() {
150                if (_service == null) {
151                        _service = new KeplerLocalLSIDService();
152                }
153                return _service;
154        }
155
156        /*
157         * INITIALIZATION
158         */
159
160        /**
161         * Assigns the current kepler actor library. Clears current set of lsid
162         * objects. This operation is here because there isn't a great way in
163         * Kepler/Ptolemy to access "global" objects. Here, we grab the library
164         * model that is initially given to Kepler when the graph editor starts up.
165         * 
166         *@param _libraryModel
167         *            The current kepler actor library
168         */
169        public void setBasicActorLibrary(EntityTreeModel _libraryModel) {
170                // clear the hashtable
171                _assignedObjects = new Hashtable();
172                // set the library model
173                this._libraryModel = _libraryModel;
174                // load up the hashtable
175                getNamedObjIds(_libraryModel.getRoot());
176
177        }
178
179        /**
180         * This operation provides access to the assigned library model.
181         * 
182         *@return The current kepler actor library
183         */
184        public EntityTreeModel getBasicActorLibrary() {
185                return _libraryModel;
186        }
187
188        // .........
189        // recursive helper function to traverse and pull out the named
190        // objects with ids
191        // .........
192        /**
193         * Gets the namedObjIds attribute of the KeplerLocalLSIDService object
194         * 
195         *@param parent
196         *            Description of the Parameter
197         */
198        private void getNamedObjIds(Object parent) {
199                // check if the parent has an id, and if so add to hashtable
200                if (parent instanceof NamedObj) {
201                        _addNamedObject((NamedObj) parent);
202                }
203                // iterator over children
204                for (int i = 0; i < _libraryModel.getChildCount(parent); i++) {
205                        getNamedObjIds(_libraryModel.getChild(parent, i));
206                }
207        }
208
209        /*
210         * MAIN LSID OPERATIONS
211         */
212
213        /**
214         * Adds a named object to an lsid that hasn't been previously assigned. The
215         * lsid cannot be previously assigned to an object. To create a well-formed
216         * lsid, use createLSID.
217         * 
218         *@param obj
219         *            The object to assign an lsid to
220         *@param lsid
221         *            Description of the Parameter
222         *@exception IllegalLSIDAssignmentException
223         *                Description of the Exception
224         *@return true if the object was assigned to the lsid correctly, false
225         *          otherwise.
226         */
227        public void assignLSID(String lsid, NamedObj obj)
228                        throws IllegalLSIDAssignmentException {
229                // the lsid must not be assigned
230                if (_assignedObjects.containsKey(lsid)) {
231                        throw new IllegalLSIDAssignmentException(lsid
232                                        + " already assigned to an object");
233                }
234
235                // make sure there isn't already an obj with the same entity name
236                // to be assigned
237                Enumeration e = _assignedObjects.elements();
238                while (e.hasMoreElements()) {
239                        NamedObj assignedObj = (NamedObj) e.nextElement();
240                        String name = assignedObj.getName();
241                        if (name.equals(obj.getName())) {
242                                String msg = "Object name '" + obj.getName()
243                                                + "' already exists in library";
244                                throw new IllegalLSIDAssignmentException(msg);
245                        }
246                }
247
248                try {
249                        // add the id
250                        NamedObjId objId = new NamedObjId(obj, NamedObjId.NAME);
251                        objId.setContainer(obj);
252                        objId.setExpression(lsid);
253                        // update the assigned objects
254                        _assignedObjects.put(lsid, obj);
255                        if (!_managedLsids.contains(lsid)) {
256                                _managedLsids.add(lsid);
257                        }
258                } catch (ptolemy.kernel.util.NameDuplicationException nde) {
259                        // don't add the entity id, but do add the lsid to the DB.
260                        _assignedObjects.put(lsid, obj);
261                        if (!_managedLsids.contains(lsid)) {
262                                _managedLsids.add(lsid);
263                        }
264                } catch (Exception ex) {
265                        throw new IllegalLSIDAssignmentException(ex.toString());
266                }
267        }
268
269        /**
270         * Updates a previously assigned lsid to be assigned the given object.
271         * Removes the old object from the actor library, and adds the given object.
272         * 
273         *@param lsid
274         *            the lsid to update
275         *@param obj
276         *            the new object to connect to the lsid
277         *@exception IllegalLSIDAssignmentException
278         *                Description of the Exception
279         */
280        public void updateLSID(String lsid, NamedObj obj)
281                        throws IllegalLSIDAssignmentException {
282                // the lsid must be assigned
283                if (!_assignedObjects.containsKey(lsid)) {
284                        throw new IllegalLSIDAssignmentException(
285                                        lsid
286                                                        + " cannot be updated because it is not assigned to an object");
287                }
288                try {
289                        NamedObj oldObj = (NamedObj) _assignedObjects.get(lsid);
290                        if (oldObj instanceof Attribute) {
291                                ((Attribute) oldObj).setContainer(null);
292                        } else if (oldObj instanceof ComponentEntity) {
293                                ((ComponentEntity) oldObj).setContainer(null);
294                        }
295                        _assignedObjects.remove(lsid);
296                        // need to remove the object from the actor library!!!
297                        assignLSID(lsid, obj);
298                } catch (Exception e) {
299                        throw new IllegalLSIDAssignmentException(e.toString());
300                }
301        }
302
303        /*
304         * LSID HELPER OPERATIONS
305         */
306        /**
307         *@param namespace
308         *            Description of the Parameter
309         *@param id
310         *            Description of the Parameter
311         *@exception IllegalLSIDException
312         *                Description of the Exception
313         *@return an lsid with the default domain and the given namespace and id
314         */
315        public String createLSID(String namespace, String id)
316                        throws IllegalLSIDException {
317                String lsid = URN + ":" + LSID + ":" + DOMAIN + ":" + namespace + ":"
318                                + id;
319                if (!isWellFormedLSID(lsid)) {
320                        throw new IllegalLSIDException(lsid);
321                }
322                return lsid;
323        }
324
325        /**
326         * This is a simple algorithm that computes a new id for a given namespace
327         * in the default domain. Appends a LOCALID to the id, so shouldn't be
328         * duplicated in other ids in the same domain.
329         * 
330         *@param namespace
331         *            Description of the Parameter
332         *@return a unqiue lsid, relevant to assigned lsids, for the given
333         *          namespace.
334         */
335        public String createLocallyUniqueLSID(String namespace) {
336                try {
337                        boolean found = false;
338                        Integer id = (Integer) _lastDomainId.get(namespace);
339                        if (id == null)
340                                id = new Integer(-1);
341                        int newId = id.intValue();
342                        while (!found) {
343                                newId = newId + 1;
344                                String lsid = createLSID(namespace, "LOCALID" + newId);
345                                if (!isAssignedLSID(lsid)) {
346                                        found = true;
347                                        if (_lastDomainId.contains(namespace)) {
348                                                _lastDomainId.remove(namespace);
349                                        }
350                                        _lastDomainId.put(namespace, new Integer(newId));
351                                }
352                        }
353                        return createLSID(namespace, "LOCALID" + newId);
354                } catch (Exception e) {
355                        e.printStackTrace();
356                }
357                return null;
358        }
359
360        /**
361         *@param obj
362         *            Description of the Parameter
363         *@return The lSIDFor value
364         */
365        public Iterator getLSIDFor(NamedObj obj) {
366                Vector result = new Vector();
367
368                if (_assignedObjects.containsValue(obj)) {
369                        Enumeration keys = _assignedObjects.keys();
370                        // for each key, check if the associated object equals
371                        // this object and if so, add to result
372                        while (keys.hasMoreElements()) {
373                                String lsid = (String) keys.nextElement();
374                                NamedObj tmp = (NamedObj) _assignedObjects.get(lsid);
375                                if (tmp.equals(obj)) {
376                                        result.add(lsid);
377                                }
378                        }
379                }
380                return result.iterator();
381        }
382
383        /**
384         *@param lsid
385         *            the lsid to test
386         *@return true if the given lsid is known to the service
387         */
388        public boolean isAssignedLSID(String lsid) {
389                return _managedLsids.contains(lsid);
390        }
391
392        /**
393         *@param lsid
394         *            the lsid to test
395         *@return true if the lsid is well formed, false otherwise
396         */
397        public boolean isWellFormedLSID(String lsid) {
398                String delim = ":";
399                StringTokenizer strTok = new StringTokenizer(lsid, delim, false);
400                String str = "";
401                try {
402                        // urn part
403                        str = strTok.nextToken();
404                        if (!str.equals(URN)) {
405                                return false;
406                        }
407                        // lsid
408                        str = strTok.nextToken();
409                        if (!str.equals(LSID)) {
410                                return false;
411                        }
412                        // the domain
413                        str = strTok.nextToken();
414                        if (containsWhiteSpace(str)) {
415                                return false;
416                        }
417                        // the namespace
418                        str = strTok.nextToken();
419                        if (containsWhiteSpace(str)) {
420                                return false;
421                        }
422                        // the id
423                        str = strTok.nextToken();
424                        if (containsWhiteSpace(str)) {
425                                return false;
426                        }
427                        // the optional version
428                        if (strTok.hasMoreTokens()
429                                        && containsWhiteSpace(strTok.nextToken())) {
430                                return false;
431                        }
432                        // no more tokens
433                        if (strTok.hasMoreTokens()) {
434                                return false;
435                        }
436                        return true;
437                } catch (Exception e) {
438                        return false;
439                }
440        }
441
442        /**
443         * Description of the Method
444         * 
445         *@param str
446         *            Description of the Parameter
447         *@return Description of the Return Value
448         */
449        private boolean containsWhiteSpace(String str) {
450                StringTokenizer strTok = new StringTokenizer(str);
451                return (strTok.countTokens() != 1);
452        }
453
454        /**
455         *@param lsid
456         *            Description of the Parameter
457         *@return null if not a resolvable lsid (i.e., unknown lsid)
458         */
459        public NamedObj getData(String lsid) {
460                return (NamedObj) _assignedObjects.get(lsid);
461        }
462
463        /**
464         * Adds a new NamedObj instance to the engine
465         * 
466         *@param obj
467         *            the instance to add, which must have an NamedObjId property
468         *@return true if the item was added successfully
469         */
470        private boolean _addNamedObject(NamedObj obj) {
471                List idAtts = obj.attributeList(NamedObjId.class);
472                Iterator iter = idAtts.iterator();
473                boolean result = iter.hasNext();
474                while (iter.hasNext()) {
475                        NamedObjId id = (NamedObjId) iter.next();
476                        // check if it is already there ...
477                        if (!_assignedObjects.containsKey(id.getExpression())) {
478                                String lsid = id.getExpression();
479                                if (!_managedLsids.contains(lsid)) {
480                                        _managedLsids.add(lsid);
481                                }
482                                _assignedObjects.put(lsid, obj);
483                        }
484                }
485                return result;
486        }
487
488        /** TODO: throw error */
489        public void commitChanges() {
490
491                try {
492                        // open the KEPLERACTORLIB file
493                        File file = new File(KEPLERACTORLIB);
494                        BufferedWriter output = new BufferedWriter(new FileWriter(file));
495
496                        // write out the header of the file
497                        output.write("<?xml version=\"1.0\" standalone=\"no\"?>\n");
498                        output
499                                        .write("<!DOCTYPE plot PUBLIC \"-//UC Berkeley//DTD MoML 1//EN\"\n");
500                        output
501                                        .write("\"http://ptolemy.eecs.berkeley.edu/xml/dtd/MoML_1.dtd\">\n");
502                        output.write("<group>\n");
503
504                        // add each unique item in the hashtable to root
505                        Vector objsToAdd = new Vector();
506                        Enumeration e = _assignedObjects.elements();
507                        while (e.hasMoreElements()) {
508                                NamedObj obj = (NamedObj) e.nextElement();
509                                if (!objsToAdd.contains(obj)) {
510                                        objsToAdd.add(obj);
511                                }
512                        }
513
514                        for (Iterator i = objsToAdd.iterator(); i.hasNext();) {
515                                NamedObj obj = (NamedObj) i.next();
516                                output.write("\n" + obj.exportMoML() + "\n");
517                        }
518                        output.write("</group>\n");
519
520                        // close the file
521                        output.close();
522
523                } catch (Exception e) {
524                        e.printStackTrace();
525                }
526
527        }
528
529        /**
530         * Description of the Method
531         * 
532         *@return Description of the Return Value
533         */
534        public Iterator assignedLSIDs() {
535                return _managedLsids.iterator();
536        }
537
538        /*
539         * Testing
540         */
541
542        // for testing
543        /**
544         * The main program for the KeplerLocalLSIDService class
545         * 
546         *@param args
547         *            The command line arguments
548         */
549        public static void main(String[] args) {
550                KeplerLocalLSIDService serv = KeplerLocalLSIDService.instance();
551                try {
552                        System.out.println(">>> Assigning const");
553                        String lsid = serv.createLSID("bowers", "myFavoriteService");
554                        Const c = new Const(new EntityLibrary(), "myConst");
555                        serv.assignLSID(lsid, c);
556
557                        System.out.println(">>> Assigning another const");
558                        String lsid0 = serv.createLSID("bowers", "myOtherFavoriteService");
559                        System.out.println(">>> Updating const");
560                        Const c0 = new Const(new EntityLibrary(), "myFufuConst");
561                        serv.updateLSID(lsid, c0);
562
563                        serv.commitChanges();
564
565                } catch (Exception e) {
566                        e.printStackTrace();
567                }
568        }
569
570}