001    /*
002     * 
003     * $Revision: 15621 $ $Date: 2009-07-25 08:32:01 +0200 (Sat, 25 Jul 2009) $
004     *
005     * This file is part of ***  M y C o R e  ***
006     * See 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, in a file called gpl.txt or 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.datamodel.metadata;
025    
026    import java.io.UnsupportedEncodingException;
027    import java.net.URLEncoder;
028    import java.text.DecimalFormat;
029    import java.util.HashMap;
030    
031    import org.apache.log4j.Logger;
032    import org.mycore.common.MCRConfiguration;
033    import org.mycore.common.MCRException;
034    import org.mycore.datamodel.common.MCRXMLTableManager;
035    
036    /**
037     * This class holds all informations and methods to handle the MyCoRe Object ID.
038     * The MyCoRe Object ID is a special ID to identify each metadata object with
039     * three parts, they are the project identifier, the type identifier and a
040     * string with a number.
041     * 
042     * @author Jens Kupferschmidt
043     * @author Thomas Scheffler (yagee)
044     * @version $Revision: 15621 $ $Date: 2009-07-25 08:32:01 +0200 (Sat, 25 Jul 2009) $
045     */
046    public final class MCRObjectID {
047        /**
048         * public constant value for the MCRObjectID length
049         */
050        public static final int MAX_LENGTH = 64;
051    
052        // configuration values
053        protected static MCRConfiguration CONFIG;
054    
055        private static final Logger LOGGER = Logger.getLogger(MCRObjectID.class);
056    
057        // counter for the next IDs per project base ID
058        private static HashMap<String, Integer> lastnumber = new HashMap<String, Integer>();
059    
060        // data of the ID
061        private String mcr_project_id = null;
062    
063        private String mcr_type_id = null;
064    
065        private String mcr_id = null;
066    
067        private int mcr_number = -1;
068    
069        private boolean mcr_valid_id = false;
070    
071        private static int number_distance = 1;
072    
073        private static String number_pattern = null;
074    
075        private static DecimalFormat number_format = null;
076    
077        /**
078         * Static method to load the configuration.
079         */
080        static {
081            CONFIG = MCRConfiguration.instance();
082            number_distance = CONFIG.getInt("MCR.Metadata.ObjectID.NumberDistance", 1);
083            number_pattern = CONFIG.getString("MCR.Metadata.ObjectID.NumberPattern", "0000000000");
084            number_format = new DecimalFormat(number_pattern);
085        }
086    
087        /**
088         * The constructor for an empty MCRObjectId.
089         */
090        public MCRObjectID() {
091        }
092    
093        /**
094         * The constructor for MCRObjectID from a given string.
095         * 
096         * @exception MCRException
097         *                if the given string is not valid.
098         */
099        public MCRObjectID(String id) throws MCRException {
100            mcr_valid_id = false;
101    
102            if (!setID(id)) {
103                throw new MCRException("The ID is not valid: " + id);
104            }
105    
106            mcr_valid_id = true;
107        }
108    
109        /**
110         * The method set the MCRObjectID from a given base ID string. The number
111         * was computed from this methode. It is the next free number of an item in
112         * the database for the given project ID and type ID.
113         * 
114         * @exception MCRException
115         *                if the given string is not valid or can't connect to the
116         *                MCRXMLTableManager.
117         */
118        public void setNextFreeId() throws MCRException {
119            setNextFreeId(getBase());
120        }
121    
122        /**
123         * The method set the MCRObjectID from a given base ID string. A base ID is
124         * <em>project_id</em>_<em>type_id</em>. The number was computed from
125         * this methode. It is the next free number of an item in the database for
126         * the given project ID and type ID.
127         * 
128         * @param base_id
129         *            the basic ID
130         * @exception MCRException
131         *                if the given string is not valid or can't connect to the
132         *                MCRXMLTableManager.
133         */
134        public synchronized void setNextFreeId(String base_id) throws MCRException {
135            setNextFreeId(base_id, 0);
136        }
137        
138        public synchronized void setNextFreeId(String base_id, int maxInWorkflow) throws MCRException {
139            // check the base_id
140            mcr_valid_id = false;
141    
142            if( ! setID( base_id + "_1" ) ) {
143                throw new MCRException("Error in project base string:" + base_id );
144            }
145    
146            int last = Math.max(getLastID(base_id).getNumberAsInteger(), maxInWorkflow) + 1;
147            int rest = last % number_distance;
148            if (rest != 0)
149                last += number_distance - rest;
150            lastnumber.put(base_id, last);
151            setID(base_id + "_" + String.valueOf(last));
152        }
153    
154        /**
155         * Returns the last ID used or reserved for the given object base type.
156         */
157        public static MCRObjectID getLastID(String base_id) {
158            MCRObjectID oid = new MCRObjectID(base_id + "_1");
159            int last = lastnumber.containsKey(base_id) ? lastnumber.get(base_id) : 0;
160            int stored = MCRXMLTableManager.instance().getHighestStoredID(oid.getProjectId(), oid.getTypeId());
161            oid.setNumber(Math.max(last, stored));
162            return oid;
163        }
164    
165        /**
166         * This method set a new type in a existing MCRObjectID.
167         * 
168         * @param type
169         *            the new type
170         */
171        public final boolean setType(String type) {
172            if (type == null) {
173                return false;
174            }
175    
176            String test = type.toLowerCase().intern();
177    
178            if (!CONFIG.getBoolean("MCR.Metadata.Type." + test, false)) {
179                return false;
180            }
181    
182            mcr_type_id = test;
183    
184            return true;
185        }
186    
187        /**
188         * This method set a new number in a existing MCRObjectID.
189         * 
190         * @param num
191         *            the new number
192         */
193        public final boolean setNumber(int num) {
194            if (!mcr_valid_id) {
195                return false;
196            }
197    
198            if (num < 0) {
199                return false;
200            }
201    
202            mcr_number = num;
203            mcr_id = null;
204    
205            return true;
206        }
207    
208        /**
209         * This method get the string with <em>project_id</em>. If the ID is not
210         * valid, an empty string was returned.
211         * 
212         * @return the string of the project id
213         */
214        public final String getProjectId() {
215            if (!mcr_valid_id) {
216                return "";
217            }
218    
219            return mcr_project_id;
220        }
221    
222        /**
223         * This method get the string with <em>type_id</em>. If the ID is not
224         * valid, an empty string was returned.
225         * 
226         * @return the string of the type id
227         */
228        public final String getTypeId() {
229            if (!mcr_valid_id) {
230                return "";
231            }
232    
233            return mcr_type_id;
234        }
235    
236        /**
237         * This method get the string with <em>number</em>. If the ID is not
238         * valid, an empty string was returned.
239         * 
240         * @return the string of the number
241         */
242        public final String getNumberAsString() {
243            if (!mcr_valid_id) {
244                return "";
245            }
246    
247            return number_format.format(mcr_number);
248        }
249    
250        /**
251         * This method get the integer with <em>number</em>. If the ID is not
252         * valid, a -1 was returned.
253         * 
254         * @return the number as integer
255         */
256        public final int getNumberAsInteger() {
257            if (!mcr_valid_id) {
258                return -1;
259            }
260    
261            return mcr_number;
262        }
263    
264        /**
265         * This method get the basic string with <em>project_id</em>_
266         * <em>type_id</em>. If the Id is not valid, an empty string was
267         * returned.
268         * 
269         * @return the string of the schema name
270         */
271        public String getBase() {
272            if (!mcr_valid_id) {
273                return "";
274            }
275    
276            return mcr_project_id + "_" + mcr_type_id;
277        }
278    
279        /**
280         * This method get the ID string with <em>project_id</em>_
281         * <em>type_id</em>_<em>number</em>. If the ID is not valid, an empty
282         * string was returned.
283         * 
284         * @return the string of the schema name
285         */
286        public String getId() {
287            if (!mcr_valid_id) {
288                return "";
289            }
290    
291            if (mcr_id == null) {
292                mcr_id = new StringBuffer(MAX_LENGTH).append(mcr_project_id).append('_').append(mcr_type_id).append('_').append(number_format.format(mcr_number)).toString();
293            }
294    
295            return mcr_id;
296        }
297    
298        /**
299         * This method return the validation value of a MCRObjectId. The MCRObjectID
300         * is valid if:
301         * <ul>
302         * <li>The syntax of the ID is <em>project_id</em>_<em>type_id</em>_
303         * <em>number</em> as <em>String_String_Integer</em>.
304         * <li>The ID is not longer as MAX_LENGTH.
305         * </ul>
306         * 
307         * @return the validation value, true if the MCRObjectId is correct,
308         *         otherwise return false
309         */
310        public boolean isValid() {
311            return mcr_valid_id;
312        }
313    
314        /**
315         * This method return the validation value of a MCRObjectId and store the
316         * components in this class. The <em>type_id</em> was set to lower case.
317         * The MCRObjectID is valid if:
318         * <ul>
319         * <li>The argument is not null.
320         * <li>The syntax of the ID is <em>project_id</em>_<em>type_id</em>_
321         * <em>number</em> as <em>String_String_Integer</em>.
322         * <li>The ID is not longer as MAX_LENGTH.
323         * <li>The ID has only characters, they must not encoded.
324         * </ul>
325         * 
326         * @param id
327         *            the MCRObjectId
328         * @return the validation value, true if the MCRObjectId is correct,
329         *         otherwise return false
330         */
331        public final boolean setID(String id) {
332            mcr_valid_id = false;
333    
334            if ((id == null) || ((id = id.trim()).length() == 0)) {
335                return false;
336            }
337    
338            if (id.length() > MAX_LENGTH) {
339                return false;
340            }
341    
342            String mcr_id;
343    
344            try {
345                mcr_id = URLEncoder.encode(id, CONFIG.getString("MCR.Request.CharEncoding", "UTF-8"));
346            } catch (UnsupportedEncodingException e1) {
347                LOGGER.error("MCR.Request.CharEncoding property does not contain a valid encoding:", e1);
348    
349                return false;
350            }
351    
352            if (!mcr_id.equals(id)) {
353                return false;
354            }
355    
356            int len = mcr_id.length();
357            int i = mcr_id.indexOf("_");
358    
359            if (i == -1) {
360                return false;
361            }
362    
363            mcr_project_id = mcr_id.substring(0, i).intern();
364    
365            int j = mcr_id.indexOf("_", i + 1);
366    
367            if (j == -1) {
368                return false;
369            }
370    
371            mcr_type_id = mcr_id.substring(i + 1, j).toLowerCase().intern();
372    
373            if (!CONFIG.getBoolean("MCR.Metadata.Type." + mcr_type_id.toLowerCase(), false)) {
374                return false;
375            }
376    
377            mcr_number = -1;
378    
379            try {
380                mcr_number = Integer.parseInt(mcr_id.substring(j + 1, len));
381            } catch (NumberFormatException e) {
382                return false;
383            }
384    
385            if (mcr_number < 0) {
386                return false;
387            }
388    
389            this.mcr_id = null;
390            mcr_valid_id = true;
391    
392            return mcr_valid_id;
393        }
394    
395        /**
396         * This method checks the value of a MCRObjectId. The MCRObjectId is valid
397         * if:
398         * <ul>
399         * <li>The argument is not null.
400         * <li>The syntax of the ID is <em>project_id</em>_<em>type_id</em>_
401         * <em>number</em> as <em>String_String_Integer</em>.
402         * <li>The ID is not longer as MAX_LENGTH. >li> The ID has only characters,
403         * they must not encoded.
404         * </ul>
405         * 
406         * @param id
407         *            the MCRObjectId
408         * @throws MCRException
409         *             if ID is not valid
410         */
411        public static void isValidOrDie(String id) {
412            new MCRObjectID(id);
413        }
414    
415        /**
416         * This method check this data again the input and retuns the result as
417         * boolean.
418         * 
419         * @param in
420         *            the MCRObjectID to check
421         * @return true if all parts are equal, else return false.
422         */
423        public boolean equals(MCRObjectID in) {
424            return (mcr_project_id.equals(in.mcr_project_id) && (mcr_type_id.equals(in.mcr_type_id)) && (mcr_number == in.mcr_number));
425        }
426    
427        /**
428         * This method check this data again the input and retuns the result as
429         * boolean.
430         * 
431         * @param in
432         *            the MCRObjectID to check
433         * @return true if all parts are equal, else return false.
434         * @see java.lang.Object#equals(Object)
435         */
436        public boolean equals(Object in) {
437            if (!(in instanceof MCRObjectID)) {
438                return false;
439            }
440            return equals((MCRObjectID) in);
441        }
442    
443        /**
444         * @see #getId()
445         * @see java.lang.Object#toString()
446         */
447        public String toString() {
448            return getId();
449        }
450    
451        /**
452         * returns getId().hashCode()
453         * 
454         * @see #getId()
455         * @see java.lang.Object#hashCode()
456         */
457        public int hashCode() {
458            return getId().hashCode();
459        }
460    }