]> git.aero2k.de Git - tmp/jakarta-migration.git/commitdiff
CSPACE-234: Initial work on revising interfaces for ID Parts, still in progress....
authorAron Roberts <aron@socrates.berkeley.edu>
Wed, 18 Nov 2009 04:47:45 +0000 (04:47 +0000)
committerAron Roberts <aron@socrates.berkeley.edu>
Wed, 18 Nov 2009 04:47:45 +0000 (04:47 +0000)
31 files changed:
services/id/service/src/main/java/org/collectionspace/services/id/part/AlgorithmicIDPart.java [new file with mode: 0644]
services/id/service/src/main/java/org/collectionspace/services/id/part/AlphabeticSequenceIDPart.java [new file with mode: 0644]
services/id/service/src/main/java/org/collectionspace/services/id/part/DateIDPart.java [new file with mode: 0644]
services/id/service/src/main/java/org/collectionspace/services/id/part/DynamicValueIDPart.java [new file with mode: 0644]
services/id/service/src/main/java/org/collectionspace/services/id/part/GregorianDateIDPart.java [new file with mode: 0644]
services/id/service/src/main/java/org/collectionspace/services/id/part/GregorianDateIDPartOutputFormatter.java [new file with mode: 0644]
services/id/service/src/main/java/org/collectionspace/services/id/part/IDPart.java [new file with mode: 0644]
services/id/service/src/main/java/org/collectionspace/services/id/part/IDPartAlgorithm.java [new file with mode: 0644]
services/id/service/src/main/java/org/collectionspace/services/id/part/IDPartOutputFormatter.java [new file with mode: 0644]
services/id/service/src/main/java/org/collectionspace/services/id/part/IDPartRegexValidator.java [new file with mode: 0644]
services/id/service/src/main/java/org/collectionspace/services/id/part/IDPartValidator.java [new file with mode: 0644]
services/id/service/src/main/java/org/collectionspace/services/id/part/JavaPrintfIDPartOutputFormatter.java [new file with mode: 0644]
services/id/service/src/main/java/org/collectionspace/services/id/part/JavaRandomNumberIDPartAlgorithm.java [new file with mode: 0644]
services/id/service/src/main/java/org/collectionspace/services/id/part/NoOpIDPartOutputFormatter.java [new file with mode: 0644]
services/id/service/src/main/java/org/collectionspace/services/id/part/NoOpIDPartValidator.java [new file with mode: 0644]
services/id/service/src/main/java/org/collectionspace/services/id/part/NonEmptyIDPartValidator.java [new file with mode: 0644]
services/id/service/src/main/java/org/collectionspace/services/id/part/NumericIDPartRegexValidator.java [new file with mode: 0644]
services/id/service/src/main/java/org/collectionspace/services/id/part/NumericSequenceIDPart.java [new file with mode: 0644]
services/id/service/src/main/java/org/collectionspace/services/id/part/RandomNumberIDPart.java [new file with mode: 0644]
services/id/service/src/main/java/org/collectionspace/services/id/part/SequenceIDPart.java [new file with mode: 0644]
services/id/service/src/main/java/org/collectionspace/services/id/part/StaticValueIDPart.java [new file with mode: 0644]
services/id/service/src/main/java/org/collectionspace/services/id/part/UUIDPart.java [new file with mode: 0644]
services/id/service/src/main/java/org/collectionspace/services/id/part/UUIDType4IDPartAlgorithm.java [new file with mode: 0644]
services/id/service/src/main/java/org/collectionspace/services/id/part/UUIDType4PartRegexValidator.java [new file with mode: 0644]
services/id/service/src/test/java/org/collectionspace/services/id/part/test/GregorianDateIDPartTest.java [new file with mode: 0644]
services/id/service/src/test/java/org/collectionspace/services/id/part/test/NoOpIDPartValidatorTest.java [new file with mode: 0644]
services/id/service/src/test/java/org/collectionspace/services/id/part/test/NonEmptyIDPartValidatorTest.java [new file with mode: 0644]
services/id/service/src/test/java/org/collectionspace/services/id/part/test/NumericIDPartRegexValidatorTest.java [new file with mode: 0644]
services/id/service/src/test/java/org/collectionspace/services/id/part/test/NumericSequenceIDPartTest.java [new file with mode: 0644]
services/id/service/src/test/java/org/collectionspace/services/id/part/test/RandomNumberIDPartTest.java [new file with mode: 0644]
services/id/service/src/test/java/org/collectionspace/services/id/part/test/UUIDPartTest.java [new file with mode: 0644]

diff --git a/services/id/service/src/main/java/org/collectionspace/services/id/part/AlgorithmicIDPart.java b/services/id/service/src/main/java/org/collectionspace/services/id/part/AlgorithmicIDPart.java
new file mode 100644 (file)
index 0000000..44d4a80
--- /dev/null
@@ -0,0 +1,19 @@
+package org.collectionspace.services.id.part;
+
+public abstract class AlgorithmicIDPart implements IDPart, DynamicValueIDPart {
+
+    @Override
+    public abstract IDPartOutputFormatter getOutputFormatter();
+
+    @Override
+    public abstract IDPartValidator getValidator();
+
+    public abstract IDPartAlgorithm getAlgorithm();
+
+    @Override
+    public String newID() {
+        return getOutputFormatter().format(getAlgorithm().generateID());
+    }
+
+}
+
diff --git a/services/id/service/src/main/java/org/collectionspace/services/id/part/AlphabeticSequenceIDPart.java b/services/id/service/src/main/java/org/collectionspace/services/id/part/AlphabeticSequenceIDPart.java
new file mode 100644 (file)
index 0000000..6974535
--- /dev/null
@@ -0,0 +1,78 @@
+package org.collectionspace.services.id.part;
+
+// @TODO Largely unimplemented at present.
+// Corresponding test class has not yet been created.
+
+public class AlphabeticSequenceIDPart extends SequenceIDPart {
+
+    // @TODO Externalize character sequences to their own class.
+    private AlphabeticSequenceIDPart.AlphabeticCharSequence charsInSequence;
+    private IDPartOutputFormatter formatter;
+    private IDPartValidator validator;
+    private char initialValue;
+
+    public AlphabeticSequenceIDPart () {
+    }
+
+    @Override
+    public IDPartOutputFormatter getOutputFormatter () {
+        return formatter;
+    }
+
+    public void setOutputFormatter (IDPartOutputFormatter formatter) {
+        this.formatter = formatter;
+    }
+
+    @Override
+    public IDPartValidator getValidator() {
+        return this.validator;
+    }
+
+    @Override
+    public boolean hasCurrentID() {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public String getCurrentID() {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public void setCurrentID(String s) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public String getInitialID() {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public String nextID() {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    public char getInitialValue () {
+        return initialValue;
+    }
+
+    public void setInitialValue (char val) {
+        this.initialValue = val;
+    }
+
+    public AlphabeticSequenceIDPart.AlphabeticCharSequence getCharsInSequence () {
+        return charsInSequence;
+    }
+
+    public void setCharsInSequence
+        (AlphabeticSequenceIDPart.AlphabeticCharSequence val) {
+        this.charsInSequence = val;
+    }
+
+    public enum AlphabeticCharSequence {
+        ;
+    }
+
+}
+
diff --git a/services/id/service/src/main/java/org/collectionspace/services/id/part/DateIDPart.java b/services/id/service/src/main/java/org/collectionspace/services/id/part/DateIDPart.java
new file mode 100644 (file)
index 0000000..3949b62
--- /dev/null
@@ -0,0 +1,18 @@
+package org.collectionspace.services.id.part;
+
+public abstract class DateIDPart implements IDPart, DynamicValueIDPart {
+
+    public DateIDPart () {
+    }
+
+    @Override
+    public abstract IDPartOutputFormatter getOutputFormatter();
+
+    @Override
+    public abstract IDPartValidator getValidator();
+
+    @Override
+    public abstract String newID();
+
+}
+
diff --git a/services/id/service/src/main/java/org/collectionspace/services/id/part/DynamicValueIDPart.java b/services/id/service/src/main/java/org/collectionspace/services/id/part/DynamicValueIDPart.java
new file mode 100644 (file)
index 0000000..9edfc73
--- /dev/null
@@ -0,0 +1,6 @@
+package org.collectionspace.services.id.part;
+
+public interface DynamicValueIDPart extends IDPart {
+
+}
+
diff --git a/services/id/service/src/main/java/org/collectionspace/services/id/part/GregorianDateIDPart.java b/services/id/service/src/main/java/org/collectionspace/services/id/part/GregorianDateIDPart.java
new file mode 100644 (file)
index 0000000..725ae9e
--- /dev/null
@@ -0,0 +1,68 @@
+package org.collectionspace.services.id.part;
+
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+
+public class GregorianDateIDPart extends DateIDPart {
+
+    private GregorianDateIDPartOutputFormatter formatter;
+    private IDPartValidator validator = IDPart.DEFAULT_VALIDATOR;
+
+    public GregorianDateIDPart(String formatPattern) {
+        setFormatter(new GregorianDateIDPartOutputFormatter(formatPattern));
+    }
+
+    public GregorianDateIDPart(String formatPattern, String languageCode) {
+        setFormatter(
+            new GregorianDateIDPartOutputFormatter(formatPattern, languageCode));
+    }
+
+    public GregorianDateIDPart(String formatPattern, IDPartValidator validator) {
+        setFormatter(new GregorianDateIDPartOutputFormatter(formatPattern));
+        setValidator(validator);
+    }
+
+    @Override
+    public IDPartOutputFormatter getOutputFormatter() {
+       return this.formatter;
+    }
+
+    @Override
+    public IDPartValidator getValidator() {
+        return this.validator;
+    }
+
+    @Override
+    public String newID() {
+        
+        // Get the current time instance.
+               Calendar cal = GregorianCalendar.getInstance();
+        
+        // @TODO Implement time zone awareness.
+        //
+        // Relevant time zones may include:
+        // - A time zone for the server environment.
+        // - A default time zone for the tenant.
+        // - A user-specific time zone, acquired externally
+        //   (e.g. from a client when called from that context).
+        // cal.setTimeZone(TimeZone tz);
+
+        // The following value is coerced to a String, representing
+        // milliseconds in the Epoch, for conformance with the contract
+        // that output formatters act on Strings.
+        //
+        // In the formatter, this value is converted back into a date
+        // and date-specific formatting is applied.
+        return getOutputFormatter().format(Long.toString(cal.getTime().getTime()));
+    }
+
+    private void setFormatter(GregorianDateIDPartOutputFormatter formatter) {
+        this.formatter = formatter;
+    }
+
+    private void setValidator(IDPartValidator validator) {
+        throw new UnsupportedOperationException("Not yet implemented");
+    }
+
+}
+
diff --git a/services/id/service/src/main/java/org/collectionspace/services/id/part/GregorianDateIDPartOutputFormatter.java b/services/id/service/src/main/java/org/collectionspace/services/id/part/GregorianDateIDPartOutputFormatter.java
new file mode 100644 (file)
index 0000000..83ccf3d
--- /dev/null
@@ -0,0 +1,151 @@
+package org.collectionspace.services.id.part;
+
+import java.util.Date;
+import java.util.Locale;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+
+import java.util.Arrays;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+// Uses Java's interpreter for printf-style format strings.
+// http://java.sun.com/javase/1.6/docs/api/java/util/Formatter.html
+
+public class GregorianDateIDPartOutputFormatter implements IDPartOutputFormatter {
+
+    final Logger logger =
+        LoggerFactory.getLogger(GregorianDateIDPartOutputFormatter.class);
+
+    private int maxOutputLength = DEFAULT_MAX_OUTPUT_LENGTH;
+    private Locale locale = null;
+    private String language;
+    private String formatPattern;
+
+    public GregorianDateIDPartOutputFormatter(String formatPattern) {
+        setFormatPattern(formatPattern);
+        setLocale(Locale.getDefault());
+    }
+
+    public GregorianDateIDPartOutputFormatter(String formatPattern,
+            String languageCode) {
+        setFormatPattern(formatPattern);
+        setLocale(languageCode);
+    }
+
+    @Override
+    public int getMaxOutputLength () {
+        return this.maxOutputLength;
+    }
+
+    public void setMaxOutputLength (int length) {
+        this.maxOutputLength = length;
+    }
+
+    @Override
+    public String getFormatPattern () {
+        return this.formatPattern;
+    }
+
+    public void setFormatPattern(String pattern) {
+        if (pattern == null || pattern.trim().isEmpty()) {
+            logger.error("Format pattern cannot be null or empty.");
+        } else {
+            this.formatPattern = pattern;
+        }
+    }
+
+    @Override
+    public String format(String id) {
+
+        Long millisecondsInEpoch = 0L;
+        try {
+            millisecondsInEpoch = (Long.parseLong(id));
+        } catch (NumberFormatException e) {
+            logger.error("Could not parse date milliseconds as a number.", e);
+            return "";
+        }
+        
+        String formattedID = "";
+        if (millisecondsInEpoch > 0) {
+          Date d = new Date(millisecondsInEpoch);
+          formattedID = formatDate(d);
+
+          // @TODO Check for exceeding maximum length before
+          // returning formatted date value.
+        }
+
+        return formattedID;
+    }
+
+    public String formatDate(Date date) {
+        SimpleDateFormat dateformatter;
+        if (getLocale() != null) {
+            dateformatter = new SimpleDateFormat(getFormatPattern(), getLocale());
+        } else {
+            dateformatter = new SimpleDateFormat(getFormatPattern());
+        }
+        return dateformatter.format(date);
+    }
+
+    // @TODO Consider generalizing locale-specific operations
+    // in a utility class outside of ID package.
+
+    public Locale getLocale() {
+        return this.locale;
+    }
+
+    public void setLocale(Locale locale) {
+        this.locale = locale;
+    }
+
+    public void setLocale(String languageCode) {
+
+        if (languageCode == null || languageCode.trim().isEmpty()) {
+            logger.error("Locale language code cannot be null or empty.");
+            return;
+        }
+
+        if (languageCode.length() != 2) {
+            logger.error(
+                "Locale language code '" + languageCode +
+                "' must be a two-letter ISO-639 language code.");
+            return;
+        }
+
+        // Although language codes are documented as required to be
+        // in lowercase, and they are output in that way in
+        // DateFormat.getAvailableLocales(), they appear to be
+        // matched - within Locales - in a case-insensitive manner.
+        /*
+        if (! languageCode.equals(languageCode.toLowerCase())) {
+            logger.error("Locale language code must be in lower case.");
+            return;
+        }
+        */
+
+        Locale l = new Locale(languageCode, "");
+        if (isValidLocaleForDateFormatter(l)) {
+            setLocale(l);
+        } else {
+            logger.error("Locale language code '" + languageCode +
+                "' cannot be used for formatting dates.");
+            return;
+        }
+    }
+    
+    private boolean isValidLocaleForDateFormatter(Locale l) {
+        Locale[] locales = DateFormat.getAvailableLocales();
+        return (Arrays.asList(locales).contains(l)) ? true : false;
+    }
+
+    private boolean isValidLength(String id) {
+        if (id.length() > getMaxOutputLength()) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+}
+
diff --git a/services/id/service/src/main/java/org/collectionspace/services/id/part/IDPart.java b/services/id/service/src/main/java/org/collectionspace/services/id/part/IDPart.java
new file mode 100644 (file)
index 0000000..ac3240c
--- /dev/null
@@ -0,0 +1,15 @@
+package org.collectionspace.services.id.part;
+
+public interface IDPart {
+
+    IDPartOutputFormatter DEFAULT_FORMATTER = new NoOpIDPartOutputFormatter();
+    IDPartValidator DEFAULT_VALIDATOR = new NoOpIDPartValidator();
+
+    public IDPartOutputFormatter getOutputFormatter();
+
+    public IDPartValidator getValidator();
+
+    public String newID();
+
+}
+
diff --git a/services/id/service/src/main/java/org/collectionspace/services/id/part/IDPartAlgorithm.java b/services/id/service/src/main/java/org/collectionspace/services/id/part/IDPartAlgorithm.java
new file mode 100644 (file)
index 0000000..8fc76ad
--- /dev/null
@@ -0,0 +1,7 @@
+package org.collectionspace.services.id.part;
+
+public interface IDPartAlgorithm {
+
+    public String generateID();
+
+}
diff --git a/services/id/service/src/main/java/org/collectionspace/services/id/part/IDPartOutputFormatter.java b/services/id/service/src/main/java/org/collectionspace/services/id/part/IDPartOutputFormatter.java
new file mode 100644 (file)
index 0000000..3010251
--- /dev/null
@@ -0,0 +1,14 @@
+package org.collectionspace.services.id.part;
+
+public interface IDPartOutputFormatter {
+
+    public static int DEFAULT_MAX_OUTPUT_LENGTH = Integer.MAX_VALUE;
+
+    public int getMaxOutputLength();
+
+    public String getFormatPattern();
+
+    public String format(String id);
+
+}
+
diff --git a/services/id/service/src/main/java/org/collectionspace/services/id/part/IDPartRegexValidator.java b/services/id/service/src/main/java/org/collectionspace/services/id/part/IDPartRegexValidator.java
new file mode 100644 (file)
index 0000000..d094b7e
--- /dev/null
@@ -0,0 +1,47 @@
+package org.collectionspace.services.id.part;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class IDPartRegexValidator implements IDPartValidator {
+
+    final Logger logger = LoggerFactory.getLogger(IDPartRegexValidator.class);
+
+    @Override
+    public boolean isValid(String id) {
+
+        if (id == null) {
+            return false;
+        }
+
+        boolean isValid = false;
+        try {
+            Pattern pattern = Pattern.compile(getRegexPattern());
+            Matcher matcher = pattern.matcher(id);
+            if (matcher.matches()) {
+                isValid = true;
+            }
+        // @TODO Validation will fail by default if the regex pattern
+        // cannot be compiled.  We may wish to consider re-throwing this
+        // Exception as an IllegalStateException, to raise this issue for
+        // timely resolution.
+        } catch (PatternSyntaxException e) {
+            String regex = getRegexPattern();
+            String msg =
+                (regex == null || regex.trim().isEmpty()) ?
+                "Could not validate ID due to null or empty regex pattern." :
+                "Could not validate ID due to invalid regex pattern: " + regex;
+            logger.error(msg, e);
+        }
+
+        return isValid;
+
+    }
+
+    public abstract String getRegexPattern();
+
+}
diff --git a/services/id/service/src/main/java/org/collectionspace/services/id/part/IDPartValidator.java b/services/id/service/src/main/java/org/collectionspace/services/id/part/IDPartValidator.java
new file mode 100644 (file)
index 0000000..1c2cd59
--- /dev/null
@@ -0,0 +1,7 @@
+package org.collectionspace.services.id.part;
+
+public interface IDPartValidator {
+
+    public boolean isValid(String id);
+
+}
diff --git a/services/id/service/src/main/java/org/collectionspace/services/id/part/JavaPrintfIDPartOutputFormatter.java b/services/id/service/src/main/java/org/collectionspace/services/id/part/JavaPrintfIDPartOutputFormatter.java
new file mode 100644 (file)
index 0000000..ac06702
--- /dev/null
@@ -0,0 +1,77 @@
+package org.collectionspace.services.id.part;
+
+import java.util.IllegalFormatException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+// Uses Java's interpreter for printf-style format strings.
+// http://java.sun.com/javase/1.6/docs/api/java/util/Formatter.html
+
+public class JavaPrintfIDPartOutputFormatter implements IDPartOutputFormatter {
+
+    StringWriter stringwriter = new StringWriter();
+    private int maxOutputLength = DEFAULT_MAX_OUTPUT_LENGTH;
+    private String formatPattern;
+
+    public JavaPrintfIDPartOutputFormatter () {
+    }
+
+    @Override
+    public int getMaxOutputLength () {
+        return this.maxOutputLength;
+    }
+
+    public void setMaxOutputLength (int length) {
+        this.maxOutputLength = length;
+    }
+
+    @Override
+    public String getFormatPattern () {
+        return this.formatPattern;
+    }
+
+    public void setFormatPattern(String pattern) {
+        this.formatPattern = pattern;
+    }
+
+    @Override
+    public String format(String id) {
+
+        String formattedID = id;
+
+        String pattern = getFormatPattern();
+
+        // If the formatting pattern is empty, just check length.
+        if (pattern == null || pattern.trim().isEmpty()) {
+            isValidLength(formattedID);
+
+        // Otherwise, format the ID using the pattern, then check length.
+        } else {
+            // Clear the StringWriter's buffer from its last usage. 
+            StringBuffer buf = stringwriter.getBuffer();
+            buf.setLength(0);
+            // Apply the formatting pattern to the ID.
+            try {
+                PrintWriter printwriter = new PrintWriter(stringwriter);
+                printwriter.printf(id, pattern);
+                formattedID = stringwriter.toString();
+            } catch(IllegalFormatException e) {
+                // @TODO Log and handle this exception.
+            }
+            isValidLength(formattedID);
+        }
+
+        return formattedID;
+    }
+
+    // Check whether the formatted ID exceeds the specified maximum length.
+
+    private void isValidLength(String id) {
+        if (id.length() > getMaxOutputLength()) {
+            // @TODO Log error, possibly throw exception.
+        }
+
+    }
+
+}
+
diff --git a/services/id/service/src/main/java/org/collectionspace/services/id/part/JavaRandomNumberIDPartAlgorithm.java b/services/id/service/src/main/java/org/collectionspace/services/id/part/JavaRandomNumberIDPartAlgorithm.java
new file mode 100644 (file)
index 0000000..7820282
--- /dev/null
@@ -0,0 +1,46 @@
+
+package org.collectionspace.services.id.part;
+
+import java.util.Random;
+
+public class JavaRandomNumberIDPartAlgorithm implements IDPartAlgorithm {
+
+    // @TODO Verify whether this simple singleton pattern is
+    // achieving the goal of using a single instance of the random
+    // number generator.
+
+    // @TODO Check whether we might need to store a serialization
+    // of this class, once instantiated, between invocations, and
+    // load the class from its serialized state.
+
+    // @TODO Look into whether we may have some user stories or use cases
+    // that require the use of java.security.SecureRandom, rather than
+    // java.util.Random.
+
+    // Starting with Java 5, the default instantiation of Random()
+    // sets the seed "to a value very likely to be distinct from any
+    // other invocation of this constructor."
+    private Random r = new Random();
+
+    private JavaRandomNumberIDPartAlgorithm() {
+    }
+
+    // See http://en.wikipedia.org/wiki/Singleton_pattern
+    private static class SingletonHolder {
+        private static final JavaRandomNumberIDPartAlgorithm INSTANCE =
+            new JavaRandomNumberIDPartAlgorithm();
+    }
+
+    public static JavaRandomNumberIDPartAlgorithm getInstance() {
+        return SingletonHolder.INSTANCE;
+    }
+
+    // @TODO Allow setting a maximum value.
+    @Override
+    public String generateID(){
+        // Returns a value between 0 (inclusive) and the
+        // maximum value of an int.
+        return Integer.toString(r.nextInt(Integer.MAX_VALUE));
+    }
+
+}
diff --git a/services/id/service/src/main/java/org/collectionspace/services/id/part/NoOpIDPartOutputFormatter.java b/services/id/service/src/main/java/org/collectionspace/services/id/part/NoOpIDPartOutputFormatter.java
new file mode 100644 (file)
index 0000000..57f4c2b
--- /dev/null
@@ -0,0 +1,33 @@
+package org.collectionspace.services.id.part;
+
+public class NoOpIDPartOutputFormatter implements IDPartOutputFormatter {
+
+    public NoOpIDPartOutputFormatter () {
+    }
+
+    @Override
+    public int getMaxOutputLength () {
+        return Integer.MAX_VALUE;
+    }
+
+    public void setMaxOutputLength (int length) {
+        // Do nothing.
+    }
+
+    @Override
+    public String getFormatPattern () {
+        return "";
+    }
+
+    public void setFormatPattern(String pattern) {
+        // Do nothing.
+    }
+
+    @Override
+    public String format(String id) {
+        return id;
+    }
+
+
+}
+
diff --git a/services/id/service/src/main/java/org/collectionspace/services/id/part/NoOpIDPartValidator.java b/services/id/service/src/main/java/org/collectionspace/services/id/part/NoOpIDPartValidator.java
new file mode 100644 (file)
index 0000000..35c5692
--- /dev/null
@@ -0,0 +1,13 @@
+package org.collectionspace.services.id.part;
+
+public class NoOpIDPartValidator implements IDPartValidator {
+
+    public NoOpIDPartValidator() {
+    }
+
+    @Override
+    public boolean isValid(String id) {
+        return true;
+    }
+
+}
diff --git a/services/id/service/src/main/java/org/collectionspace/services/id/part/NonEmptyIDPartValidator.java b/services/id/service/src/main/java/org/collectionspace/services/id/part/NonEmptyIDPartValidator.java
new file mode 100644 (file)
index 0000000..5ce8c57
--- /dev/null
@@ -0,0 +1,14 @@
+package org.collectionspace.services.id.part;
+
+public class NonEmptyIDPartValidator implements IDPartValidator {
+
+    @Override
+    public boolean isValid(String id) {
+        if (id == null || id.trim().isEmpty()) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+}
diff --git a/services/id/service/src/main/java/org/collectionspace/services/id/part/NumericIDPartRegexValidator.java b/services/id/service/src/main/java/org/collectionspace/services/id/part/NumericIDPartRegexValidator.java
new file mode 100644 (file)
index 0000000..e6f319a
--- /dev/null
@@ -0,0 +1,14 @@
+package org.collectionspace.services.id.part;
+
+public class NumericIDPartRegexValidator extends IDPartRegexValidator {
+
+    final static String REGEX_PATTERN = "(\\d+)";
+
+    public NumericIDPartRegexValidator() {
+    }
+
+    @Override
+    public String getRegexPattern() {
+        return REGEX_PATTERN;
+    }
+}
diff --git a/services/id/service/src/main/java/org/collectionspace/services/id/part/NumericSequenceIDPart.java b/services/id/service/src/main/java/org/collectionspace/services/id/part/NumericSequenceIDPart.java
new file mode 100644 (file)
index 0000000..88f69e8
--- /dev/null
@@ -0,0 +1,118 @@
+package org.collectionspace.services.id.part;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class NumericSequenceIDPart extends SequenceIDPart {
+
+    final Logger logger =
+        LoggerFactory.getLogger(NumericSequenceIDPart.class);
+
+    final static private long DEFAULT_INITIAL_VALUE = 1;
+    private long initialValue = DEFAULT_INITIAL_VALUE;
+
+    final static private long CURRENT_VALUE_NOT_SET = -1;
+    private long currentValue = CURRENT_VALUE_NOT_SET;
+
+    final static private long DEFAULT_INCREMENT_BY_VALUE = 1;
+    private long incrementBy = DEFAULT_INCREMENT_BY_VALUE;
+
+    // @TODO Replace the NoOp formatter with a printf formatter.
+    private IDPartOutputFormatter formatter = new NoOpIDPartOutputFormatter();
+    private IDPartValidator validator = new NumericIDPartRegexValidator();
+
+
+    public NumericSequenceIDPart() {
+    }
+
+    public NumericSequenceIDPart(long initial) {
+        setInitialID(initial);
+    }
+
+    public NumericSequenceIDPart(long initial, long incrementBy) {
+        setInitialID(initial);
+        setIncrementBy(incrementBy);
+    }
+
+    @Override
+    public IDPartOutputFormatter getOutputFormatter() {
+        return this.formatter;
+    }
+
+    public void setOutputFormatter (IDPartOutputFormatter formatter) {
+        this.formatter = formatter;
+    }
+
+    @Override
+    public IDPartValidator getValidator() {
+        return this.validator;
+    }
+
+    // newID() is implemented in superclass, SequenceIDPart.
+
+    @Override
+    public boolean hasCurrentID() {
+        return (this.currentValue == CURRENT_VALUE_NOT_SET) ? false : true;
+    }
+
+    @Override
+    public String getCurrentID() {
+        return Long.toString(this.currentValue);
+    }
+
+    public void setCurrentID(long val) {
+        if (val <= 0) {
+            logger.error("Current ID value for numeric ID sequences " +
+                "must be positive.");
+        } else {
+            this.currentValue = val;
+        }
+    }
+
+    @Override
+    public void setCurrentID(String str) {
+        try {
+            setCurrentID(Long.parseLong(str));
+        } catch (NumberFormatException e) {
+            logger.error("Could not parse current ID value as a number.", e);
+        }
+    }
+
+    @Override
+    public String getInitialID() {
+        return Long.toString(this.initialValue);
+    }
+
+    public void setInitialID(long initial) {
+        if (initial <= 0) {
+            logger.error("Current ID value for numeric ID sequences " +
+                "must be positive.");
+        } else {
+            this.initialValue = initial;
+        }
+    }
+
+    @Override
+    public String nextID() {
+        // @TODO Rethink this approach soon, as we may not want
+        // to change the current value for IDs that have been
+        // provisionally issued.
+        this.currentValue = this.currentValue + this.incrementBy;
+        return getCurrentID();
+    }
+
+    public String getIncrementBy() {
+        return Long.toString(this.incrementBy);
+    }
+
+    private void setIncrementBy(long incrementBy) {
+        if (incrementBy <= 0) {
+            logger.error("Increment-by value for numeric ID sequences " +
+                "must be positive.");
+        } else {
+            this.incrementBy = incrementBy;
+        }
+    }
+
+}
+
diff --git a/services/id/service/src/main/java/org/collectionspace/services/id/part/RandomNumberIDPart.java b/services/id/service/src/main/java/org/collectionspace/services/id/part/RandomNumberIDPart.java
new file mode 100644 (file)
index 0000000..97634f0
--- /dev/null
@@ -0,0 +1,34 @@
+package org.collectionspace.services.id.part;
+
+public class RandomNumberIDPart extends AlgorithmicIDPart {
+
+    // @TODO The name of this class may be too generic, since there
+    // may be other random number generators with different algorithms.
+
+    private IDPartOutputFormatter formatter = IDPart.DEFAULT_FORMATTER;
+    private IDPartValidator validator = new NumericIDPartRegexValidator();
+    private IDPartAlgorithm algorithm =
+        JavaRandomNumberIDPartAlgorithm.getInstance();
+
+    public RandomNumberIDPart(){
+    }
+
+    @Override
+    public IDPartOutputFormatter getOutputFormatter() {
+        return this.formatter;
+    }
+
+    @Override
+    public IDPartValidator getValidator() {
+         return this.validator;
+    }
+
+    @Override
+    public IDPartAlgorithm getAlgorithm() {
+        return this.algorithm;
+    }
+
+    // newID() is implemented in superclass, AlgorithmicIDPart.
+
+}
+
diff --git a/services/id/service/src/main/java/org/collectionspace/services/id/part/SequenceIDPart.java b/services/id/service/src/main/java/org/collectionspace/services/id/part/SequenceIDPart.java
new file mode 100644 (file)
index 0000000..882fd6b
--- /dev/null
@@ -0,0 +1,34 @@
+package org.collectionspace.services.id.part;
+
+public abstract class SequenceIDPart implements IDPart, DynamicValueIDPart {
+
+    public SequenceIDPart () {
+    }
+
+    @Override
+    public abstract IDPartOutputFormatter getOutputFormatter();
+
+    @Override
+    public abstract IDPartValidator getValidator();
+
+    @Override
+    public String newID() {
+        if (hasCurrentID()) {
+            return getOutputFormatter().format(nextID());
+        } else {
+            return getOutputFormatter().format(getInitialID());
+        }
+    }
+
+    abstract public boolean hasCurrentID();
+
+    abstract public String getCurrentID();
+
+    abstract public void setCurrentID(String s);
+
+    abstract public String getInitialID();
+
+    abstract public String nextID();
+
+}
+
diff --git a/services/id/service/src/main/java/org/collectionspace/services/id/part/StaticValueIDPart.java b/services/id/service/src/main/java/org/collectionspace/services/id/part/StaticValueIDPart.java
new file mode 100644 (file)
index 0000000..89b71fb
--- /dev/null
@@ -0,0 +1,36 @@
+package org.collectionspace.services.id.part;
+
+public class StaticValueIDPart implements IDPart {
+
+    private IDPartOutputFormatter formatter;
+    private IDPartValidator validator;
+    private String initialValue;
+
+    public StaticValueIDPart() {
+    }
+
+    @Override
+    public String newID() {
+        return getInitialValue();
+    }
+
+    @Override
+    public IDPartOutputFormatter getOutputFormatter() {
+        return this.formatter;
+    }
+
+    @Override
+    public IDPartValidator getValidator() {
+        return this.validator;
+    }
+
+    public String getInitialValue() {
+        return this.initialValue;
+    }
+
+    public void setInitialValue(String val) {
+        this.initialValue = val;
+    }
+
+}
+
diff --git a/services/id/service/src/main/java/org/collectionspace/services/id/part/UUIDPart.java b/services/id/service/src/main/java/org/collectionspace/services/id/part/UUIDPart.java
new file mode 100644 (file)
index 0000000..07d9698
--- /dev/null
@@ -0,0 +1,33 @@
+package org.collectionspace.services.id.part;
+
+public class UUIDPart extends AlgorithmicIDPart {
+
+    // @TODO The name of this class may be too generic, since
+    // there may be other UUID generators with different algorithms.
+
+    private IDPartOutputFormatter formatter = IDPart.DEFAULT_FORMATTER;
+    private IDPartValidator validator = new UUIDType4PartRegexValidator();
+    private IDPartAlgorithm algorithm = new UUIDType4IDPartAlgorithm();
+
+    public UUIDPart() {
+    }
+
+    @Override
+    public IDPartOutputFormatter getOutputFormatter () {
+        return this.formatter;
+    }
+
+    @Override
+    public IDPartValidator getValidator() {
+        return this.validator;
+    }
+
+    @Override
+    public IDPartAlgorithm getAlgorithm() {
+        return this.algorithm;
+    }
+
+    // newID() is implemented in superclass, AlgorithmicIDPart.
+
+}
+
diff --git a/services/id/service/src/main/java/org/collectionspace/services/id/part/UUIDType4IDPartAlgorithm.java b/services/id/service/src/main/java/org/collectionspace/services/id/part/UUIDType4IDPartAlgorithm.java
new file mode 100644 (file)
index 0000000..2337b58
--- /dev/null
@@ -0,0 +1,15 @@
+package org.collectionspace.services.id.part;
+
+import java.util.UUID;
+
+public class UUIDType4IDPartAlgorithm implements IDPartAlgorithm {
+
+    public UUIDType4IDPartAlgorithm(){
+    }
+
+    @Override
+    public String generateID(){
+        return UUID.randomUUID().toString();
+    }
+
+}
diff --git a/services/id/service/src/main/java/org/collectionspace/services/id/part/UUIDType4PartRegexValidator.java b/services/id/service/src/main/java/org/collectionspace/services/id/part/UUIDType4PartRegexValidator.java
new file mode 100644 (file)
index 0000000..568c89d
--- /dev/null
@@ -0,0 +1,31 @@
+package org.collectionspace.services.id.part;
+
+public class UUIDType4PartRegexValidator extends IDPartRegexValidator {
+
+    // @TODO The name of this class may be too generic, since
+    // there may be other UUID generators with different algorithms.
+
+    final static String REGEX_PATTERN =
+        "(" +
+        "[a-z0-9\\-]{8}" +
+        "\\-" +
+        "[a-z0-9\\-]{4}" +
+        "\\-" +
+        "4" +
+        "[a-z0-9\\-]{3}" +
+        "\\-" +
+        "[89ab]" +
+        "[a-z0-9\\-]{3}" +
+        "\\-" +
+        "[a-z0-9\\-]{12}" +
+        ")";
+
+    public UUIDType4PartRegexValidator() {
+    }
+
+    @Override
+    public String getRegexPattern() {
+        return REGEX_PATTERN;
+    }
+
+}
diff --git a/services/id/service/src/test/java/org/collectionspace/services/id/part/test/GregorianDateIDPartTest.java b/services/id/service/src/test/java/org/collectionspace/services/id/part/test/GregorianDateIDPartTest.java
new file mode 100644 (file)
index 0000000..9f1f405
--- /dev/null
@@ -0,0 +1,50 @@
+package org.collectionspace.services.id.part.test;
+
+import org.collectionspace.services.id.part.GregorianDateIDPart;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class GregorianDateIDPartTest {
+
+    final Logger logger =
+        LoggerFactory.getLogger(GregorianDateIDPartTest.class);
+
+    GregorianDateIDPart part;
+
+    @Test
+    public void newID() {
+
+        // @TODO Replace these hard-coded expedients, which will all fail
+        // when the current month or year doesn't match these asserted values.
+        
+        part = new GregorianDateIDPart("yyyy");
+        Assert.assertEquals(part.newID(), "2009");
+
+        part = new GregorianDateIDPart("M");
+        Assert.assertEquals(part.newID(), "11");
+
+        part = new GregorianDateIDPart("MMMM");
+        Assert.assertEquals(part.newID(), "November");
+
+        part = new GregorianDateIDPart("MMMM", "fr");
+        // Month names are not capitalized in French.
+        Assert.assertEquals(part.newID(), "novembre");
+        
+    }
+
+
+    @Test
+    public void format() {
+    }
+
+    @Test
+    public void isValid() {
+        part = new GregorianDateIDPart("yyyy");
+        Assert.assertTrue(part.getValidator().isValid(part.newID()));
+    }
+
+}
diff --git a/services/id/service/src/test/java/org/collectionspace/services/id/part/test/NoOpIDPartValidatorTest.java b/services/id/service/src/test/java/org/collectionspace/services/id/part/test/NoOpIDPartValidatorTest.java
new file mode 100644 (file)
index 0000000..aa8c151
--- /dev/null
@@ -0,0 +1,18 @@
+package org.collectionspace.services.id.part.test;
+
+import org.collectionspace.services.id.part.NoOpIDPartValidator;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class NoOpIDPartValidatorTest {
+
+    @Test
+    public void isValid() {
+        NoOpIDPartValidator validator = new NoOpIDPartValidator();
+        Assert.assertTrue(validator.isValid(null));
+        Assert.assertTrue(validator.isValid(""));
+        Assert.assertTrue(validator.isValid("any string"));
+    }
+
+}
diff --git a/services/id/service/src/test/java/org/collectionspace/services/id/part/test/NonEmptyIDPartValidatorTest.java b/services/id/service/src/test/java/org/collectionspace/services/id/part/test/NonEmptyIDPartValidatorTest.java
new file mode 100644 (file)
index 0000000..e7af409
--- /dev/null
@@ -0,0 +1,23 @@
+package org.collectionspace.services.id.part.test;
+
+import org.collectionspace.services.id.part.NonEmptyIDPartValidator;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class NonEmptyIDPartValidatorTest {
+
+    NonEmptyIDPartValidator validator = new NonEmptyIDPartValidator();
+
+    @Test
+    public void isValid() {
+        Assert.assertTrue(validator.isValid("any string"));
+    }
+
+    @Test(dependsOnMethods = {"isValid"})
+    public void isValidWithInvalidValues() {
+        Assert.assertFalse(validator.isValid(null));
+        Assert.assertFalse(validator.isValid(""));
+    }
+
+}
diff --git a/services/id/service/src/test/java/org/collectionspace/services/id/part/test/NumericIDPartRegexValidatorTest.java b/services/id/service/src/test/java/org/collectionspace/services/id/part/test/NumericIDPartRegexValidatorTest.java
new file mode 100644 (file)
index 0000000..85e5425
--- /dev/null
@@ -0,0 +1,27 @@
+package org.collectionspace.services.id.part.test;
+
+import org.collectionspace.services.id.part.NumericIDPartRegexValidator;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class NumericIDPartRegexValidatorTest {
+
+    NumericIDPartRegexValidator validator = new NumericIDPartRegexValidator();
+
+    @Test
+    public void isValid() {
+        Assert.assertTrue(validator.isValid("0"));
+        Assert.assertTrue(validator.isValid("5"));
+        Assert.assertTrue(validator.isValid("123456789012345"));
+    }
+
+    @Test(dependsOnMethods = {"isValid"})
+    public void isValidWithInvalidValues() {
+        Assert.assertFalse(validator.isValid(null));
+        Assert.assertFalse(validator.isValid(""));
+        Assert.assertFalse(validator.isValid("non-numeric value"));
+        Assert.assertFalse(validator.isValid("-1"));
+    }
+
+}
diff --git a/services/id/service/src/test/java/org/collectionspace/services/id/part/test/NumericSequenceIDPartTest.java b/services/id/service/src/test/java/org/collectionspace/services/id/part/test/NumericSequenceIDPartTest.java
new file mode 100644 (file)
index 0000000..1e53bf9
--- /dev/null
@@ -0,0 +1,79 @@
+package org.collectionspace.services.id.part.test;
+
+import org.collectionspace.services.id.part.SequenceIDPart;
+import org.collectionspace.services.id.part.NumericSequenceIDPart;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class NumericSequenceIDPartTest {
+
+    final Logger logger =
+        LoggerFactory.getLogger(NumericSequenceIDPartTest.class);
+
+    SequenceIDPart part;
+    String id;
+
+    @Test
+    public void newIDWithDefaultInitialValue() {
+        part = new NumericSequenceIDPart();
+        id = part.newID();
+        Assert.assertEquals(id, "1");
+        part.setCurrentID(id);
+
+        id = part.newID();
+        Assert.assertEquals(id, "2");
+        part.setCurrentID(id);
+
+        id = part.newID();
+        Assert.assertEquals(id, "3");
+        part.setCurrentID(id);
+
+        id = part.newID();
+        Assert.assertEquals(id, "4");
+    }
+
+    @Test
+    public void newIDWithSuppliedInitialValue() {
+         part = new NumericSequenceIDPart(100);
+        id = part.newID();
+        Assert.assertEquals(id, "100");
+        part.setCurrentID(id);
+
+        id = part.newID();
+        Assert.assertEquals(id, "101");
+        part.setCurrentID(id);
+
+        id = part.newID();
+        Assert.assertEquals(id, "102");
+    }
+
+    @Test
+    public void newIDWithIncrementByValue() {
+        part = new NumericSequenceIDPart(5,5);
+        id = part.newID();
+        Assert.assertEquals(id, "5");
+        part.setCurrentID(id);
+
+        id = part.newID();
+        Assert.assertEquals(id, "10");
+        part.setCurrentID(id);
+
+        id = part.newID();
+        Assert.assertEquals(id, "15");
+    }
+
+    @Test
+    public void format() {
+    }
+
+    @Test
+    public void isValid() {
+        part = new NumericSequenceIDPart();
+        Assert.assertTrue(part.getValidator().isValid(part.newID()));
+    }
+
+}
diff --git a/services/id/service/src/test/java/org/collectionspace/services/id/part/test/RandomNumberIDPartTest.java b/services/id/service/src/test/java/org/collectionspace/services/id/part/test/RandomNumberIDPartTest.java
new file mode 100644 (file)
index 0000000..1a5b0eb
--- /dev/null
@@ -0,0 +1,39 @@
+package org.collectionspace.services.id.part.test;
+
+import org.collectionspace.services.id.part.IDPart;
+import org.collectionspace.services.id.part.RandomNumberIDPart;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+public class RandomNumberIDPartTest {
+
+    IDPart part;
+    String firstID;
+    String secondID;
+    String thirdID;
+
+    @BeforeTest
+    public void setUp() {
+        part = new RandomNumberIDPart();
+        firstID = part.newID();
+        secondID = part.newID();
+        thirdID = part.newID();
+    }
+
+    @Test
+    public void newIDGeneratesNonRepeatingIDs() {
+        Assert.assertTrue(firstID.compareTo(secondID) != 0);
+        Assert.assertTrue(firstID.compareTo(thirdID) != 0);
+        Assert.assertTrue(secondID.compareTo(thirdID) != 0);
+    }
+
+    @Test
+    public void isValid() {
+        Assert.assertTrue(part.getValidator().isValid(firstID));
+        Assert.assertTrue(part.getValidator().isValid(secondID));
+        Assert.assertTrue(part.getValidator().isValid(thirdID));
+    }
+
+}
diff --git a/services/id/service/src/test/java/org/collectionspace/services/id/part/test/UUIDPartTest.java b/services/id/service/src/test/java/org/collectionspace/services/id/part/test/UUIDPartTest.java
new file mode 100644 (file)
index 0000000..bc05e38
--- /dev/null
@@ -0,0 +1,54 @@
+package org.collectionspace.services.id.part.test;
+
+import org.collectionspace.services.id.part.IDPart;
+import org.collectionspace.services.id.part.UUIDPart;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+
+public class UUIDPartTest {
+    
+    IDPart part;
+    String firstID;
+    String secondID;
+    String thirdID;
+
+    @BeforeTest
+    public void setUp() {
+        part = new UUIDPart();
+        firstID = part.newID();
+        secondID = part.newID();
+        thirdID = part.newID();
+    }
+
+    @Test
+    public void newIDGeneratesNonRepeatingIDs() {
+        Assert.assertTrue(firstID.compareTo(secondID) != 0);
+        Assert.assertTrue(firstID.compareTo(thirdID) != 0);
+        Assert.assertTrue(secondID.compareTo(thirdID) != 0);
+    }
+
+    @Test
+    public void isValid() {
+        Assert.assertTrue(part.getValidator().isValid(firstID));
+        Assert.assertTrue(part.getValidator().isValid(secondID));
+        Assert.assertTrue(part.getValidator().isValid(thirdID));
+    }
+
+    @Test(dependsOnMethods = {"isValid"})
+    public void isValidWithInvalidValues() {
+        Assert.assertFalse(part.getValidator().isValid(null));
+        Assert.assertFalse(part.getValidator().isValid(""));
+        Assert.assertFalse(part.getValidator().isValid("not a UUID"));
+        Assert.assertFalse(part.getValidator().isValid("12345"));
+        // Invalid character in 15th position (should be '4').
+        Assert.assertFalse(part.getValidator().isValid(
+            "4c9395a8-1669-31f9-806c-920d86e40912"));
+        // Invalid character in 20th position
+        // (should be '8', '9', 'a', or 'b').
+        Assert.assertFalse(part.getValidator().isValid(
+            "4c9395a8-1669-41f9-106c-920d86e40912"));
+    }
+
+}