001/*
002 * Copyright (c) 2003-2010 The Regents of the University of California.
003 * All rights reserved.
004 *
005 * '$Author: jianwu $'
006 * '$Date: 2012-11-15 20:03:09 +0000 (Thu, 15 Nov 2012) $' 
007 * '$Revision: 31093 $'
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.kepler.objectmanager.lsid;
031
032import java.io.IOException;
033import java.io.ObjectInputStream;
034import java.io.ObjectOutputStream;
035import java.io.ObjectStreamField;
036import java.io.Serializable;
037import java.util.StringTokenizer;
038
039import org.apache.commons.logging.Log;
040import org.apache.commons.logging.LogFactory;
041import org.kepler.util.AuthNamespace;
042
043/**
044 * A kepler class to store lsids of the form
045 * urn:lsid:&ltauthority>:<namespace>:<object>:<revision>#<anchor> where
046 * <authority> and <namespace> are strings that do not contain : characters and
047 * where <object> and <revision> are Long, 64 bit integers, and <anchor> is any
048 * string that does not contain a # or : character.
049 * 
050 *@created June 20, 2005
051 */
052
053public class KeplerLSID implements Serializable {
054        private static final long serialVersionUID = 6124781256944559026L;
055        private static final Log log = LogFactory
056                        .getLog(KeplerLSID.class.getName());
057        private static final boolean isDebugging = log.isDebugEnabled();
058
059        private String _lsidStr = null;
060
061        // the parts of the lsid
062        private String _authority;
063        private String _namespace;
064        private Long _object;
065        private Long _revision;
066        private String _anchor;
067
068        public static final char separatorChar = ':';
069        public static final String separator = ":";
070        public static final char anchorSeparatorChar = '#';
071        public static final String anchorSeparator = "#";
072
073        /*
074         * The following are the different types of Kepler objects. These are used
075         * to identify entries in KAR files. These are going away soon.
076         */
077        public static final String ACTOR_METADATA = "actorMetadata";
078        public static final String JAR = "jar";
079        public static final String JAVA_CLASS = "class";
080        public static final String WORKFLOW = "workflow";
081        public static final String NATIVE_LIBRARY = "nativeLibrary";
082        public static final String XML_METADATA = "xmlMetadata";
083        public static final String DATA = "data";
084        public static final String RESOURCE_FILE = "file";
085
086        /**
087         * Construct an lsid.
088         * @throws Exception
089         */
090        public KeplerLSID(String lsidString) throws Exception {
091                if (isDebugging)
092                        log.debug("KeplerLSID("+lsidString+")");
093                // Note - if any changes needs to be made to this constructor,
094                // the changes should be made in initializeLSID so the
095                // deserialization readObject() method can benefit from it as well.
096                _lsidStr = lsidString;
097                initializeLSID();
098        }
099
100        /**
101         * construct an lsid from components
102         * 
103         * @param authority
104         *            the authority of the new lsid
105         * @param namespace
106         *            the namespace of the new lsid
107         * @param the
108         *            object number of the new lsid
109         * @param revision
110         *            the revision of the new lsid
111         */
112        public KeplerLSID(String authority, String namespace, Long object,
113                        Long revision) throws Exception {
114                if (isDebugging)
115                        log.debug("KeplerLSID("+authority+","+namespace+","+object+","+revision+")");
116
117                _lsidStr = "urn:lsid:" + authority + ":" + namespace + ":" + object
118                                + ":" + revision;
119                initializeLSID();
120        }
121
122        /**
123         * creates an lsid from a metacat docid of the form
124         * <document>.<object>.<rev>
125         * 
126         * @param metacatDocid
127         *            the docid to translate
128         * @param authority
129         *            the authority to use in the lsid
130         */
131        public KeplerLSID(String metacatDocid, String authority) throws Exception {
132                if (isDebugging) log.debug("KeplerLSID("+metacatDocid+","+authority+")");
133                String doc;
134                String obj;
135                String rev = "1";
136                doc = metacatDocid.substring(0, metacatDocid.indexOf("."));
137                // check if there is a revision
138                if (metacatDocid.lastIndexOf(".") != metacatDocid.indexOf(".")) {
139                        // there is a revision
140                        obj = metacatDocid.substring(metacatDocid.indexOf(".") + 1,
141                                        metacatDocid.lastIndexOf("."));
142                        rev = metacatDocid.substring(metacatDocid.lastIndexOf(".") + 1,
143                                        metacatDocid.length());
144                } else {
145                        // no revision
146                        obj = metacatDocid.substring(metacatDocid.indexOf(".") + 1,
147                                        metacatDocid.lastIndexOf("."));
148                }
149                _authority = authority;
150                _namespace = doc;
151                _object = new Long(obj);
152                _revision = new Long(rev);
153                _lsidStr = "urn:lsid:" + authority + ":" + _namespace + ":" + _object
154                                + ":" + _revision;
155                if (isDebugging)
156                        log.debug("string: " + _lsidStr);
157        }
158
159        /**
160         * returns the namespace component of this lsid
161         */
162        public String getNamespace() {
163                return _namespace;
164        }
165
166        /**
167         * returns the authority component of this lsid
168         */
169        public String getAuthority() {
170                return _authority;
171        }
172
173        /**
174         * return the revision componenent of this lsid
175         */
176        public Long getRevision() {
177                return _revision;
178        }
179
180        /**
181         * returns the object component of this lsid
182         */
183        public Long getObject() {
184                return _object;
185        }
186
187        public String getAnchor() {
188                return _anchor;
189        }
190
191        public void setAnchor(String anchor) throws Exception {
192                if (anchor.indexOf( separatorChar ) >= 0) {
193                        throw new Exception("Anchor may not contain the : character.");
194                }
195                if (anchor.indexOf( anchorSeparatorChar ) >= 0) {
196                        throw new Exception("Anchor may not contain the "
197                                        + anchorSeparator + " character.");
198                }
199                if (hasAnchor()) {
200                        _lsidStr = "urn" + separator + "lsid" + separator + getAuthority() 
201                                        + separator + getNamespace()
202                                        + separator + getObject() + separator + getRevision() 
203                                        + anchorSeparator + anchor;
204                } else {
205                        _lsidStr += anchorSeparator + _anchor;
206                }
207                _anchor = anchor;
208        }
209
210        /**
211         * @return true if this KeplerLSID has an anchor
212         */
213        public boolean hasAnchor() {
214                if (_anchor == null || _anchor.equals("")) {
215                        return false;
216                }
217                return true;
218        }
219
220        /**
221         * increment the revision number by 1.
222         */
223        public void incrementRevision() {
224                long rev = Long.valueOf(_revision);
225                rev++;
226                _revision = Long.valueOf(rev);
227                _lsidStr = toString();
228        }
229        
230        /**
231         * Specifically set the revision on this LSID.  You should never be calling this!
232         * Instead, pass your LSID to LSIDGenerator.updateLsidRevision(KeplerLSID).
233         * 
234         * @param newRevision
235         */
236        public void setRevision(Long newRevision) {
237                _revision = newRevision;
238                _lsidStr = toString();
239        }
240
241        /**
242         * If this LSID was generated by this Kepler Instance return true else
243         * return false
244         * 
245         * @return true if this KeplerLSID was generated by this Kepler Instance.
246         */
247        public boolean isLocalToInstance() {
248                AuthNamespace an = AuthNamespace.getInstance();
249                if (an.getAuthority().equals(getAuthority())
250                                && an.getNamespace().equals(getNamespace())) {
251                        return true;
252                }
253                return false;
254        }
255
256        /**
257         * this will create a valid filename out of the lsid (without an extension).
258         * It will replace any invalid characters in the lsid with '.' This function
259         * is irreversible.
260         */
261        public String createFilename() {
262                String fname = "urn.lsid." + getAuthority() + "." + getNamespace()
263                                + "." + getObject() + "." + getRevision();
264                // if (hasAnchor()) {
265                // fname += "." + getAnchor();
266                // }
267                fname = fname.replace('/', '.');
268                fname = fname.replace('\\', '_');
269                return fname;
270        }
271
272        /**
273         * return a string representation of the lsid
274         */
275        public String toString() {
276                String lsidStr = "urn" + separator + "lsid" + separator + getAuthority() 
277                                + separator + getNamespace()
278                                + separator + getObject() + separator + getRevision();
279                if (hasAnchor()) {
280                        lsidStr += anchorSeparator + getAnchor();
281                }
282                return lsidStr;
283        }
284
285        /**
286         * Return a string that contains only the first five elements of the LSID.
287         * i.e. urn:lsid:&lt;authority&gt;:&lt;namespace&gt;:&lt;object&gt;
288         * 
289         * @return String
290         */
291        public String toStringWithoutRevision() {
292                String lsidStr = "urn" + separator + "lsid"
293                                + separator + getAuthority() + separator + getNamespace()
294                                + separator + getObject();
295                return lsidStr;
296        }
297        
298        /*
299         * (non-Javadoc)
300         * 
301         * @see java.lang.Object#hashCode()
302         */
303        public int hashCode() {
304
305                int hash = 7;
306                hash = 31 * hash + (null == _authority ? 0 : _authority.hashCode());
307                hash = 31 * hash + (null == _namespace ? 0 : _namespace.hashCode());
308                hash = 31 * hash
309                                + (int) (_object.longValue() ^ (_object.longValue() >>> 32));
310                hash = 31
311                                * hash
312                                + (int) (_revision.longValue() ^ (_revision.longValue() >>> 32));
313                return hash;
314        }
315
316        /**
317         * return true if this lsid equals the passed in lsid regardless of anchor
318         */
319        public boolean equals(Object lsidObj) {
320                if (lsidObj instanceof KeplerLSID) {
321                        KeplerLSID lsid = (KeplerLSID) lsidObj;
322                        Long rev = lsid.getRevision();
323                        if ( equalsWithoutRevision(lsid)
324                                        && rev.equals(_revision)) {
325                                return true;
326                        }
327                }
328                return false;
329        }
330
331        /**
332         * return true if this lsid equals the passed in lsid regardless of 
333         * revision or anchor
334         */
335        public boolean equalsWithoutRevision(KeplerLSID lsid) {
336                String auth = lsid.getAuthority();
337                String ns = lsid.getNamespace();
338                Long obj = lsid.getObject();
339                if (auth.trim().equals(_authority.trim())
340                                && ns.trim().equals(_namespace.trim()) && obj.equals(_object)) {
341                        return true;
342                }
343                return false;
344        }
345
346        /**
347         * return true if this lsid equals the passed in lsid and the anchors are
348         * the same.
349         * 
350         * @param lsid
351         *       */
352        public boolean equalsWithAnchor(KeplerLSID lsid) {
353                if (equals(lsid) && getAnchor().equals(lsid.getAnchor())) {
354                        return true;
355                }
356                return false;
357        }
358
359        /**
360         * parses the lsid into its parts
361         * urn:lsid:&lt;authority&gt;:&lt;namespace&gt;:&lt;object&gt;:&lt;revision&gt;
362         */
363        private void initializeLSID() throws Exception {
364                if (_lsidStr == null) {
365                        // to avoid NullPointerException
366                        _lsidStr = "";
367                }
368
369                StringTokenizer st = new StringTokenizer(_lsidStr, separator);
370                if (st.countTokens() == 6) {
371                        String u = st.nextToken();
372                        String l = st.nextToken();
373                        if (u.equals("urn") && l.equals("lsid")) {
374                                _authority = st.nextToken();
375                                _namespace = st.nextToken();
376                                try {
377                                        _object = new Long(st.nextToken());
378                                } catch (NumberFormatException nfe) {
379                                        throw new Exception("Object must be a number");
380                                }
381                                try {
382                                        String rev = st.nextToken();
383                                        int anchorInd = rev.indexOf(anchorSeparatorChar);
384                                        if (anchorInd > 0) {
385                                                _revision = new Long(rev.substring(0, anchorInd));
386                                                _anchor = rev.substring(anchorInd + 1);
387                                                if (_anchor.indexOf(anchorSeparatorChar) >= 0)
388                                                        throw new Exception(
389                                                                        "Anchor '"+ _anchor + "' must not contain "
390                                                                        + anchorSeparator + " character");
391                                        } else {
392                                                _revision = new Long(rev);
393                                                _anchor = "";
394                                        }
395                                        _lsidStr = toString();
396                                } catch (NumberFormatException nfe) {
397                                        throw new Exception("Object must be a number");
398                                }
399                        } else {
400                                throw new Exception("KeplerLSID must begin with urn:lsid");
401                        }
402                } else if (st.countTokens() == 4) {
403                        handleBackwardsCompatibilityKludgeFormat1(st);
404                } else {
405                        throw new Exception(
406                                        "KeplerLSID format contains six elements urn:lsid:<authority>:<namespace>:<object>:<revision>");
407                }
408                if (isDebugging)
409                        log.debug("toString() -> " + toString());
410        }
411
412        public static boolean isKeplerLSIDFormat(String lsidStr) {
413                if (isDebugging) log.debug("isKeplerLSIDFormat("+lsidStr+")");
414                try {
415                        new KeplerLSID(lsidStr);
416                } catch (Exception e) {
417                        return false;
418                }
419                return true;
420        }
421
422        /**
423         * Define custom metadata representation consisting of a single String named
424         * "lsidString"
425         */
426        private static final ObjectStreamField[] serialPersistentFields = { new ObjectStreamField(
427                        "lsidString", String.class) };
428
429        /**
430         * Custom deserialization method. The serial representation of the
431         * KeplerLSID object is simply the toString() representation. This string
432         * representation is passed into the initializeFromString method.
433         * 
434         * @param ois
435         * @throws IOException
436         */
437        private void readObject(ObjectInputStream ois) throws IOException,
438                        ClassNotFoundException {
439                ObjectInputStream.GetField fields = ois.readFields();
440                String lsidString = (String) fields.get("lsidString", null);
441                try {
442                        _lsidStr = lsidString;
443                        initializeLSID();
444                } catch (Exception e) {
445                        throw new IOException("Malformed LSID in serialized representation");
446                }
447        }
448
449        /**
450         * Custom serialization method. The serial representation of the KeplerLSID
451         * object is simply the toString() representation.
452         * 
453         * @param oos
454         * @throws IOException
455         */
456        private void writeObject(ObjectOutputStream oos) throws IOException {
457                ObjectOutputStream.PutField fields = oos.putFields();
458                fields.put("lsidString", toString());
459                oos.writeFields();
460        }
461
462        /**
463         * BackwardsCompatibilityKludgeFormat1 is an lsid in the following format
464         * urn:lsid:ecoinformatics.org:bob.2.4 where 2 is the object id and 4 is the
465         * revision a dot is used as the separator instead of a colon See
466         * http://bugzilla.ecoinformatics.org/show_bug.cgi?id=4066
467         * 
468         * @param st
469         * @throws Exception
470         */
471        private void handleBackwardsCompatibilityKludgeFormat1(StringTokenizer st)
472                        throws Exception {
473                String u = st.nextToken();
474                String l = st.nextToken();
475                if (u.equals("urn") && l.equals("lsid")) {
476                        _authority = st.nextToken();
477                        String possibleNamespacePlusObjectPlusRevision = st.nextToken();
478                        StringTokenizer stn = new StringTokenizer(
479                                        possibleNamespacePlusObjectPlusRevision, ".");
480                        if (stn.countTokens() >= 3) {
481                                String ns = "";
482                                for (int i = 0; i < (stn.countTokens() - 2); i++) {
483                                        ns += stn.nextToken() + ".";
484                                }
485                                _namespace = ns.substring(0, ns.length() - 1);
486
487                        } else {
488                                throw new Exception("");
489                        }
490                        try {
491                                _object = new Long(stn.nextToken());
492                        } catch (NumberFormatException nfe) {
493                                throw new Exception("Object must be a number");
494                        }
495                        try {
496                                String rev = stn.nextToken();
497                                int anchorInd = rev.indexOf(anchorSeparatorChar);
498                                if (anchorInd > 0) {
499                                        _revision = new Long(rev.substring(0, anchorInd));
500                                        _anchor = rev.substring(anchorInd + 1);
501                                        if (_anchor.indexOf(anchorSeparatorChar) >= 0)
502                                                throw new Exception(
503                                                                "Anchor must not contain "
504                                                                + anchorSeparator + " character");
505                                } else {
506                                        _revision = new Long(rev);
507                                        _anchor = "";
508                                }
509                        } catch (NumberFormatException nfe) {
510                                throw new Exception("Object must be a number");
511                        }
512                }
513        }
514}