From e91d0d204c679eccfd9bfcf3fcb87a32b3d38027 Mon Sep 17 00:00:00 2001 From: Aron Roberts Date: Wed, 18 Nov 2009 04:47:45 +0000 Subject: [PATCH] CSPACE-234: Initial work on revising interfaces for ID Parts, still in progress. New parts and tests are currently in a sub-package of services/id/service. (Previous commit added only parent directory.) --- .../services/id/part/AlgorithmicIDPart.java | 19 +++ .../id/part/AlphabeticSequenceIDPart.java | 78 +++++++++ .../services/id/part/DateIDPart.java | 18 +++ .../services/id/part/DynamicValueIDPart.java | 6 + .../services/id/part/GregorianDateIDPart.java | 68 ++++++++ .../GregorianDateIDPartOutputFormatter.java | 151 ++++++++++++++++++ .../services/id/part/IDPart.java | 15 ++ .../services/id/part/IDPartAlgorithm.java | 7 + .../id/part/IDPartOutputFormatter.java | 14 ++ .../id/part/IDPartRegexValidator.java | 47 ++++++ .../services/id/part/IDPartValidator.java | 7 + .../part/JavaPrintfIDPartOutputFormatter.java | 77 +++++++++ .../part/JavaRandomNumberIDPartAlgorithm.java | 46 ++++++ .../id/part/NoOpIDPartOutputFormatter.java | 33 ++++ .../services/id/part/NoOpIDPartValidator.java | 13 ++ .../id/part/NonEmptyIDPartValidator.java | 14 ++ .../id/part/NumericIDPartRegexValidator.java | 14 ++ .../id/part/NumericSequenceIDPart.java | 118 ++++++++++++++ .../services/id/part/RandomNumberIDPart.java | 34 ++++ .../services/id/part/SequenceIDPart.java | 34 ++++ .../services/id/part/StaticValueIDPart.java | 36 +++++ .../services/id/part/UUIDPart.java | 33 ++++ .../id/part/UUIDType4IDPartAlgorithm.java | 15 ++ .../id/part/UUIDType4PartRegexValidator.java | 31 ++++ .../id/part/test/GregorianDateIDPartTest.java | 50 ++++++ .../id/part/test/NoOpIDPartValidatorTest.java | 18 +++ .../test/NonEmptyIDPartValidatorTest.java | 23 +++ .../test/NumericIDPartRegexValidatorTest.java | 27 ++++ .../part/test/NumericSequenceIDPartTest.java | 79 +++++++++ .../id/part/test/RandomNumberIDPartTest.java | 39 +++++ .../services/id/part/test/UUIDPartTest.java | 54 +++++++ 31 files changed, 1218 insertions(+) create mode 100644 services/id/service/src/main/java/org/collectionspace/services/id/part/AlgorithmicIDPart.java create mode 100644 services/id/service/src/main/java/org/collectionspace/services/id/part/AlphabeticSequenceIDPart.java create mode 100644 services/id/service/src/main/java/org/collectionspace/services/id/part/DateIDPart.java create mode 100644 services/id/service/src/main/java/org/collectionspace/services/id/part/DynamicValueIDPart.java create mode 100644 services/id/service/src/main/java/org/collectionspace/services/id/part/GregorianDateIDPart.java create mode 100644 services/id/service/src/main/java/org/collectionspace/services/id/part/GregorianDateIDPartOutputFormatter.java create mode 100644 services/id/service/src/main/java/org/collectionspace/services/id/part/IDPart.java create mode 100644 services/id/service/src/main/java/org/collectionspace/services/id/part/IDPartAlgorithm.java create mode 100644 services/id/service/src/main/java/org/collectionspace/services/id/part/IDPartOutputFormatter.java create mode 100644 services/id/service/src/main/java/org/collectionspace/services/id/part/IDPartRegexValidator.java create mode 100644 services/id/service/src/main/java/org/collectionspace/services/id/part/IDPartValidator.java create mode 100644 services/id/service/src/main/java/org/collectionspace/services/id/part/JavaPrintfIDPartOutputFormatter.java create mode 100644 services/id/service/src/main/java/org/collectionspace/services/id/part/JavaRandomNumberIDPartAlgorithm.java create mode 100644 services/id/service/src/main/java/org/collectionspace/services/id/part/NoOpIDPartOutputFormatter.java create mode 100644 services/id/service/src/main/java/org/collectionspace/services/id/part/NoOpIDPartValidator.java create mode 100644 services/id/service/src/main/java/org/collectionspace/services/id/part/NonEmptyIDPartValidator.java create mode 100644 services/id/service/src/main/java/org/collectionspace/services/id/part/NumericIDPartRegexValidator.java create mode 100644 services/id/service/src/main/java/org/collectionspace/services/id/part/NumericSequenceIDPart.java create mode 100644 services/id/service/src/main/java/org/collectionspace/services/id/part/RandomNumberIDPart.java create mode 100644 services/id/service/src/main/java/org/collectionspace/services/id/part/SequenceIDPart.java create mode 100644 services/id/service/src/main/java/org/collectionspace/services/id/part/StaticValueIDPart.java create mode 100644 services/id/service/src/main/java/org/collectionspace/services/id/part/UUIDPart.java create mode 100644 services/id/service/src/main/java/org/collectionspace/services/id/part/UUIDType4IDPartAlgorithm.java create mode 100644 services/id/service/src/main/java/org/collectionspace/services/id/part/UUIDType4PartRegexValidator.java create mode 100644 services/id/service/src/test/java/org/collectionspace/services/id/part/test/GregorianDateIDPartTest.java create mode 100644 services/id/service/src/test/java/org/collectionspace/services/id/part/test/NoOpIDPartValidatorTest.java create mode 100644 services/id/service/src/test/java/org/collectionspace/services/id/part/test/NonEmptyIDPartValidatorTest.java create mode 100644 services/id/service/src/test/java/org/collectionspace/services/id/part/test/NumericIDPartRegexValidatorTest.java create mode 100644 services/id/service/src/test/java/org/collectionspace/services/id/part/test/NumericSequenceIDPartTest.java create mode 100644 services/id/service/src/test/java/org/collectionspace/services/id/part/test/RandomNumberIDPartTest.java create mode 100644 services/id/service/src/test/java/org/collectionspace/services/id/part/test/UUIDPartTest.java 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 index 000000000..44d4a80b3 --- /dev/null +++ b/services/id/service/src/main/java/org/collectionspace/services/id/part/AlgorithmicIDPart.java @@ -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 index 000000000..6974535d4 --- /dev/null +++ b/services/id/service/src/main/java/org/collectionspace/services/id/part/AlphabeticSequenceIDPart.java @@ -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 index 000000000..3949b620b --- /dev/null +++ b/services/id/service/src/main/java/org/collectionspace/services/id/part/DateIDPart.java @@ -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 index 000000000..9edfc73ba --- /dev/null +++ b/services/id/service/src/main/java/org/collectionspace/services/id/part/DynamicValueIDPart.java @@ -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 index 000000000..725ae9ea4 --- /dev/null +++ b/services/id/service/src/main/java/org/collectionspace/services/id/part/GregorianDateIDPart.java @@ -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 index 000000000..83ccf3d50 --- /dev/null +++ b/services/id/service/src/main/java/org/collectionspace/services/id/part/GregorianDateIDPartOutputFormatter.java @@ -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 index 000000000..ac3240cac --- /dev/null +++ b/services/id/service/src/main/java/org/collectionspace/services/id/part/IDPart.java @@ -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 index 000000000..8fc76ad02 --- /dev/null +++ b/services/id/service/src/main/java/org/collectionspace/services/id/part/IDPartAlgorithm.java @@ -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 index 000000000..301025168 --- /dev/null +++ b/services/id/service/src/main/java/org/collectionspace/services/id/part/IDPartOutputFormatter.java @@ -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 index 000000000..d094b7eba --- /dev/null +++ b/services/id/service/src/main/java/org/collectionspace/services/id/part/IDPartRegexValidator.java @@ -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 index 000000000..1c2cd5950 --- /dev/null +++ b/services/id/service/src/main/java/org/collectionspace/services/id/part/IDPartValidator.java @@ -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 index 000000000..ac06702c7 --- /dev/null +++ b/services/id/service/src/main/java/org/collectionspace/services/id/part/JavaPrintfIDPartOutputFormatter.java @@ -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 index 000000000..782028280 --- /dev/null +++ b/services/id/service/src/main/java/org/collectionspace/services/id/part/JavaRandomNumberIDPartAlgorithm.java @@ -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 index 000000000..57f4c2b26 --- /dev/null +++ b/services/id/service/src/main/java/org/collectionspace/services/id/part/NoOpIDPartOutputFormatter.java @@ -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 index 000000000..35c5692fc --- /dev/null +++ b/services/id/service/src/main/java/org/collectionspace/services/id/part/NoOpIDPartValidator.java @@ -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 index 000000000..5ce8c57c4 --- /dev/null +++ b/services/id/service/src/main/java/org/collectionspace/services/id/part/NonEmptyIDPartValidator.java @@ -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 index 000000000..e6f319af9 --- /dev/null +++ b/services/id/service/src/main/java/org/collectionspace/services/id/part/NumericIDPartRegexValidator.java @@ -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 index 000000000..88f69e845 --- /dev/null +++ b/services/id/service/src/main/java/org/collectionspace/services/id/part/NumericSequenceIDPart.java @@ -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 index 000000000..97634f0a8 --- /dev/null +++ b/services/id/service/src/main/java/org/collectionspace/services/id/part/RandomNumberIDPart.java @@ -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 index 000000000..882fd6b3a --- /dev/null +++ b/services/id/service/src/main/java/org/collectionspace/services/id/part/SequenceIDPart.java @@ -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 index 000000000..89b71fba9 --- /dev/null +++ b/services/id/service/src/main/java/org/collectionspace/services/id/part/StaticValueIDPart.java @@ -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 index 000000000..07d9698d8 --- /dev/null +++ b/services/id/service/src/main/java/org/collectionspace/services/id/part/UUIDPart.java @@ -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 index 000000000..2337b5825 --- /dev/null +++ b/services/id/service/src/main/java/org/collectionspace/services/id/part/UUIDType4IDPartAlgorithm.java @@ -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 index 000000000..568c89d3e --- /dev/null +++ b/services/id/service/src/main/java/org/collectionspace/services/id/part/UUIDType4PartRegexValidator.java @@ -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 index 000000000..9f1f40533 --- /dev/null +++ b/services/id/service/src/test/java/org/collectionspace/services/id/part/test/GregorianDateIDPartTest.java @@ -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 index 000000000..aa8c15151 --- /dev/null +++ b/services/id/service/src/test/java/org/collectionspace/services/id/part/test/NoOpIDPartValidatorTest.java @@ -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 index 000000000..e7af4096e --- /dev/null +++ b/services/id/service/src/test/java/org/collectionspace/services/id/part/test/NonEmptyIDPartValidatorTest.java @@ -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 index 000000000..85e542567 --- /dev/null +++ b/services/id/service/src/test/java/org/collectionspace/services/id/part/test/NumericIDPartRegexValidatorTest.java @@ -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 index 000000000..1e53bf9f8 --- /dev/null +++ b/services/id/service/src/test/java/org/collectionspace/services/id/part/test/NumericSequenceIDPartTest.java @@ -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 index 000000000..1a5b0ebc4 --- /dev/null +++ b/services/id/service/src/test/java/org/collectionspace/services/id/part/test/RandomNumberIDPartTest.java @@ -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 index 000000000..bc05e38fc --- /dev/null +++ b/services/id/service/src/test/java/org/collectionspace/services/id/part/test/UUIDPartTest.java @@ -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")); + } + +} -- 2.47.3