001    /**
002     * 
003     * $Revision: 14766 $ $Date: 2009-02-19 10:49:52 +0100 (Thu, 19 Feb 2009) $
004     *
005     * This file is part of ** M y C o R e **
006     * Visit our homepage at http://www.mycore.de/ for details.
007     *
008     * This program is free software; you can use it, redistribute it
009     * and / or modify it under the terms of the GNU General Public License
010     * (GPL) as published by the Free Software Foundation; either version 2
011     * of the License or (at your option) any later version.
012     *
013     * This program is distributed in the hope that it will be useful, but
014     * WITHOUT ANY WARRANTY; without even the implied warranty of
015     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016     * GNU General Public License for more details.
017     *
018     * You should have received a copy of the GNU General Public License
019     * along with this program, normally in the file license.txt.
020     * If not, write to the Free Software Foundation Inc.,
021     * 59 Temple Place - Suite 330, Boston, MA  02111-1307 USA
022     *
023     **/
024    package org.mycore.services.urn;
025    
026    import java.util.Hashtable;
027    import java.util.Properties;
028    
029    import org.mycore.common.MCRConfiguration;
030    import org.mycore.common.MCRConfigurationException;
031    
032    /**
033     * Provides methods to create URNs (urn:nbn:de) and assign them to documents. A
034     * URN (uniform resource name) is a special kind of persistent identifier. This
035     * class handles URNs from the german subnamespace of NBN (national
036     * bibliographic number), these URNs all start with urn:nbn:de:... More
037     * information on persistent identifiers can be found at
038     * 
039     * http://www.persistent-identifier.de/
040     * 
041     * URNs are described by RFC 2141 and have the following syntax:
042     * urn:[NID]:[SNID]-[NISS][Checksum] NID = namespace ID, in this implementation
043     * always "nbn:de" SNID = subnamespace ID, a unique identifier for an
044     * organization or public library that creates and assigns URNs within its
045     * subnamespace NISS = namespace-specific string, a unique ID Checksum: all
046     * nbn:de URNs end with one digit that is a checksum
047     * 
048     * Example: urn:nbn:de:465-miless-20060622-213404-0017
049     * 
050     * A MyCoRe systen can generate URNs for more than one subnamespace. There must
051     * be one or more configurations that control the prefix (subnamespace) of
052     * generated URNs and the algorithm used to build new NISS within that
053     * subnamespace. Each configuration has a unique "subnamespace configuration ID"
054     * in mycore.properties, and optional additional properties depending on the
055     * implementation.
056     * 
057     * MCR.URN.SubNamespace.[ConfigID].Prefix=[URNPrefix], for example
058     * MCR.URN.SubNamespace.Essen.Prefix=urn.nbn.de:hbz:465-
059     * 
060     * @author Frank Lützenkirchen
061     */
062    public class MCRURNManager {
063    
064        /** The MCRURNStore implementation to use */
065        private static MCRURNStore store;
066    
067        /** The table of character codes for calculating the checksum */
068        private static Properties codes;
069    
070        static {
071            codes = new Properties();
072            codes.put("0", "1");
073            codes.put("1", "2");
074            codes.put("2", "3");
075            codes.put("3", "4");
076            codes.put("4", "5");
077            codes.put("5", "6");
078            codes.put("6", "7");
079            codes.put("7", "8");
080            codes.put("8", "9");
081            codes.put("9", "41");
082            codes.put("a", "18");
083            codes.put("b", "14");
084            codes.put("c", "19");
085            codes.put("d", "15");
086            codes.put("e", "16");
087            codes.put("f", "21");
088            codes.put("g", "22");
089            codes.put("h", "23");
090            codes.put("i", "24");
091            codes.put("j", "25");
092            codes.put("k", "42");
093            codes.put("l", "26");
094            codes.put("m", "27");
095            codes.put("n", "13");
096            codes.put("o", "28");
097            codes.put("p", "29");
098            codes.put("q", "31");
099            codes.put("r", "12");
100            codes.put("s", "32");
101            codes.put("t", "33");
102            codes.put("u", "11");
103            codes.put("v", "34");
104            codes.put("w", "35");
105            codes.put("x", "36");
106            codes.put("y", "37");
107            codes.put("z", "38");
108            codes.put("-", "39");
109            codes.put(":", "17");
110    
111            Object obj = MCRConfiguration.instance().getSingleInstanceOf("MCR.Persistence.URN.Store.Class");
112            store = (MCRURNStore) obj;
113        }
114    
115        /**
116         * Calculates the checksum for the given urn:nbn:de. The algorithm is
117         * specified by the "Carmen AP-4" project.
118         * 
119         * @return the checksum for the given urn:nbn:de
120         */
121        public static String buildChecksum(String urn) {
122            StringBuffer buffer = new StringBuffer();
123    
124            for (int i = 0; i < urn.length(); i++) {
125                String character = urn.substring(i, i + 1);
126                if (!codes.containsKey(character)) {
127                    String msg = "URN \"" + urn + "\" contains illegal character: '" + character + "'";
128                    throw new MCRConfigurationException(msg);
129                }
130                buffer.append(codes.getProperty(character));
131            }
132    
133            String digits = buffer.toString();
134            long sum = 0;
135            long digit = 0;
136    
137            for (int i = 0; i < digits.length(); i++) {
138                digit = Long.parseLong(digits.substring(i, i + 1));
139                sum += (digit * (i + 1));
140            }
141    
142            String quotient = String.valueOf(sum / digit);
143    
144            return quotient.substring(quotient.length() - 1);
145        }
146    
147        /** A map from configID to MCRNissBuilder objects */
148        private static Hashtable builders = new Hashtable();
149    
150        /**
151         * Builds a URN with a custom, given NISS.
152         * 
153         * @param configID
154         *            the ID of a subnamespace configuration in mycore.properties
155         * @param niss
156         *            the custom NISS
157         * @return the complete URN including prefix, NISS and calculated checksum
158         */
159        public static String buildURN(String configID, String niss) {
160            String base = "MCR.URN.SubNamespace." + configID + ".";
161            String prefix = MCRConfiguration.instance().getString(base + "Prefix");
162    
163            StringBuffer buffer = new StringBuffer(prefix);
164            buffer.append(niss);
165            buffer.append(buildChecksum(buffer.toString()));
166            return buffer.toString();
167        }
168    
169        /**
170         * Builds a URN using a MCRNISSBuilder object.
171         * 
172         * @param configID
173         *            the ID of a subnamespace configuration in mycore.properties
174         * @return the complete URN including prefix, niss and calculated checksum
175         */
176        public static synchronized String buildURN(String configID) {
177            String base = "MCR.URN.SubNamespace." + configID + ".";
178    
179            MCRNISSBuilder builder = (MCRNISSBuilder) (builders.get(configID));
180            if (builder == null) {
181                Object obj = MCRConfiguration.instance().getSingleInstanceOf(base + "NISSBuilder");
182                builder = (MCRNISSBuilder) (obj);
183                builder.init(configID);
184                builders.put(configID, builder);
185            }
186    
187            String niss = builder.buildNISS();
188            return buildURN(configID, niss);
189        }
190    
191        /**
192         * Returns true if the given URN has a valid structure and the checksum is
193         * correct.
194         */
195        public static boolean isValid(String urn) {
196            if ((urn == null) || (urn.length() < 14) || (!urn.startsWith("urn:nbn:")) || (!urn.toLowerCase().equals(urn))) {
197                return false;
198            } else {
199                String start = urn.substring(0, urn.length() - 1);
200                String check = buildChecksum(start);
201                return urn.endsWith(check);
202            }
203        }
204    
205        /** Returns true if the given urn is assigned to a document ID */
206        public static boolean isAssigned(String urn) {
207            return store.isAssigned(urn);
208        }
209    
210        /** Assigns the given urn to the given document ID */
211        public static void assignURN(String urn, String documentID) {
212            store.assignURN(urn, documentID);
213        }
214    
215        /**
216         * @return true if the given object has an urn assigned
217         * */
218        public static boolean hasURNAssigned(String objId){
219            return store.hasURNAssigned(objId);
220        }
221        
222        /** 
223         * Assigns the given urn to the given derivate ID 
224         * @param urn 
225         *      the urn to assign
226         * @param derivateID 
227         *      the id of the derivate
228         * @param path 
229         *      the path of the derivate in the internal filesystem
230         * @param filename 
231         *      the filename
232         */
233        public static void assignURN(String urn, String derivateID, String path, String filename) {
234            store.assignURN(urn, derivateID, path, filename);
235        }
236        
237        /**
238         * Retrieves the URN that is assigned to the given document ID
239         * 
240         * @return the urn, or null if no urn is assigned to this ID
241         */
242        public static String getURNforDocument(String documentID) {
243            return store.getURNforDocument(documentID);
244        }
245    
246        /**
247         * Retrieves the document ID that is assigned to the given urn
248         * 
249         * @return the ID, or null if no ID is assigned to this urn
250         */
251        public static String getDocumentIDforURN(String urn) {
252            return store.getDocumentIDforURN(urn);
253        }
254    
255        /**
256         * Removes the urn (and assigned document ID) from the persistent store
257         */
258        public static void removeURN(String urn) {
259            store.removeURN(urn);
260        }
261        
262        /**
263         * Removes the urn (and assigned document ID) from the persistent store
264         */
265        public static void removeURNByObjectID(String objID) {
266            store.removeURNByObjectID(objID);
267        }
268        
269        /**
270         * Create and Assign a new URN to the given Document
271         * Ensure that new created URNs do not allready exist in URN store
272         * @param documentID a MCRID
273         * @param configID - the configurationID of the URN Builder 
274         * @return the URN
275         */
276        public static synchronized String buildAndAssignURN(String documentID, String configID){
277            String urn=null;
278            do{
279                    urn = buildURN(configID);
280            }
281            while(isAssigned(urn)); 
282            
283            assignURN(urn, documentID);
284            return urn;
285        }
286    }