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 }