Skip to content

Commit

Permalink
Merge pull request #8698 from mwoodiupui/embargo-date
Browse files Browse the repository at this point in the history
Calculate the correct maximum for access condition start and end dates
  • Loading branch information
tdonohue committed May 31, 2023
2 parents cac0859 + 571df9b commit 894bc39
Show file tree
Hide file tree
Showing 8 changed files with 212 additions and 54 deletions.
Expand Up @@ -11,6 +11,8 @@
import java.util.Date;
import java.util.Objects;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.ResourcePolicy;
import org.dspace.authorize.service.AuthorizeService;
Expand All @@ -21,16 +23,16 @@
import org.dspace.eperson.Group;
import org.dspace.eperson.service.GroupService;
import org.dspace.util.DateMathParser;
import org.dspace.util.TimeHelpers;
import org.springframework.beans.factory.annotation.Autowired;

/**
* This class represents an option available in the submission upload section to
* set permission on a file. An option is defined by a name such as "open
* access", "embargo", "restricted access", etc. and some optional attributes to
* better clarify the constraints and input available to the user. For instance
* an embargo option could allow to set a start date not longer than 3 years,
* etc
*
* an embargo option could allow to set a start date not longer than 3 years.
*
* @author Luigi Andrea Pascarelli (luigiandrea.pascarelli at 4science.it)
*/
public class AccessConditionOption {
Expand All @@ -44,9 +46,9 @@ public class AccessConditionOption {
@Autowired
private ResourcePolicyService resourcePolicyService;

DateMathParser dateMathParser = new DateMathParser();
private static final Logger LOG = LogManager.getLogger();

/** An unique name identifying the access contion option **/
/** A unique name identifying the access condition option. **/
private String name;

/**
Expand Down Expand Up @@ -147,6 +149,9 @@ public void setEndDateLimit(String endDateLimit) {
* startDate should be null. Otherwise startDate may not be null.
* @param endDate end date of the resource policy. If {@link #getHasEndDate()} returns false,
* endDate should be null. Otherwise endDate may not be null.
* @throws SQLException passed through.
* @throws AuthorizeException passed through.
* @throws ParseException passed through (indicates problem with a date).
*/
public void createResourcePolicy(Context context, DSpaceObject obj, String name, String description,
Date startDate, Date endDate)
Expand All @@ -160,7 +165,7 @@ public void createResourcePolicy(Context context, DSpaceObject obj, String name,

/**
* Validate ResourcePolicy and after update it
*
*
* @param context DSpace context
* @param resourcePolicy ResourcePolicy to update
* @throws SQLException If database error
Expand All @@ -175,17 +180,25 @@ public void updateResourcePolicy(Context context, ResourcePolicy resourcePolicy)
}

/**
* Validate the policy properties, throws exceptions if any is not valid
*
* @param context DSpace context
* @param name Name of the resource policy
* @param startDate Start date of the resource policy. If {@link #getHasStartDate()}
* returns false, startDate should be null. Otherwise startDate may not be null.
* @param endDate End date of the resource policy. If {@link #getHasEndDate()}
* returns false, endDate should be null. Otherwise endDate may not be null.
* Validate the policy properties, throws exceptions if any is not valid.
*
* @param context DSpace context.
* @param name Name of the resource policy.
* @param startDate Start date of the resource policy. If
* {@link #getHasStartDate()} returns false, startDate
* should be null. Otherwise startDate may not be null.
* @param endDate End date of the resource policy. If
* {@link #getHasEndDate()} returns false, endDate should
* be null. Otherwise endDate may not be null.
* @throws IllegalStateException if a date is required and absent,
* a date is not required and present, or a date exceeds its
* configured maximum.
* @throws ParseException passed through.
*/
private void validateResourcePolicy(Context context, String name, Date startDate, Date endDate)
throws SQLException, AuthorizeException, ParseException {
throws IllegalStateException, ParseException {
LOG.debug("Validate policy dates: name '{}', startDate {}, endDate {}",
name, startDate, endDate);
if (getHasStartDate() && Objects.isNull(startDate)) {
throw new IllegalStateException("The access condition " + getName() + " requires a start date.");
}
Expand All @@ -199,29 +212,33 @@ private void validateResourcePolicy(Context context, String name, Date startDate
throw new IllegalStateException("The access condition " + getName() + " cannot contain an end date.");
}

DateMathParser dateMathParser = new DateMathParser();

Date latestStartDate = null;
if (Objects.nonNull(getStartDateLimit())) {
latestStartDate = dateMathParser.parseMath(getStartDateLimit());
latestStartDate = TimeHelpers.toMidnightUTC(dateMathParser.parseMath(getStartDateLimit()));
}

Date latestEndDate = null;
if (Objects.nonNull(getEndDateLimit())) {
latestEndDate = dateMathParser.parseMath(getEndDateLimit());
latestEndDate = TimeHelpers.toMidnightUTC(dateMathParser.parseMath(getEndDateLimit()));
}

LOG.debug(" latestStartDate {}, latestEndDate {}",
latestStartDate, latestEndDate);
// throw if startDate after latestStartDate
if (Objects.nonNull(startDate) && Objects.nonNull(latestStartDate) && startDate.after(latestStartDate)) {
throw new IllegalStateException(String.format(
"The start date of access condition %s should be earlier than %s from now.",
getName(), getStartDateLimit()
"The start date of access condition %s should be earlier than %s from now (%s).",
getName(), getStartDateLimit(), dateMathParser.getNow()
));
}

// throw if endDate after latestEndDate
if (Objects.nonNull(endDate) && Objects.nonNull(latestEndDate) && endDate.after(latestEndDate)) {
throw new IllegalStateException(String.format(
"The end date of access condition %s should be earlier than %s from now.",
getName(), getEndDateLimit()
"The end date of access condition %s should be earlier than %s from now (%s).",
getName(), getEndDateLimit(), dateMathParser.getNow()
));
}
}
Expand Down
65 changes: 55 additions & 10 deletions dspace-api/src/main/java/org/dspace/util/DateMathParser.java
Expand Up @@ -26,12 +26,15 @@
import java.util.TimeZone;
import java.util.regex.Pattern;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
* This class (Apache license) is copied from Apache Solr and add some tweaks to resolve unneeded dependency:
* https://raw.githubusercontent.com/apache/lucene-solr/releases/lucene-solr/7.1.0/solr/core/src/java/org/apache/solr
* /util/DateMathParser.java
* This class (Apache license) is copied from Apache Solr, adding some tweaks to
* resolve an unneeded dependency. See
* <a href='https://raw.githubusercontent.com/apache/lucene-solr/releases/lucene-solr/7.1.0/solr/core/src/java/org/apache/solr/util/DateMathParser.java'>the original</a>.
*
* <p>
* A Simple Utility class for parsing "math" like strings relating to Dates.
*
* <p>
Expand Down Expand Up @@ -78,7 +81,7 @@
* "<code>setNow</code>" in the interim). The default value of 'now' is
* the time at the moment the <code>DateMathParser</code> instance is
* constructed, unless overridden by the {@link CommonParams#NOW NOW}
* request param.
* request parameter.
* </p>
*
* <p>
Expand All @@ -88,7 +91,7 @@
* cascades to rounding of HOUR, MIN, MONTH, YEAR as well. The default
* <code>TimeZone</code> used is <code>UTC</code> unless overridden by the
* {@link CommonParams#TZ TZ}
* request param.
* request parameter.
* </p>
*
* <p>
Expand All @@ -102,6 +105,8 @@
*/
public class DateMathParser {

private static final Logger LOG = LogManager.getLogger();

public static final TimeZone UTC = TimeZone.getTimeZone("UTC");

/**
Expand All @@ -119,12 +124,12 @@ public class DateMathParser {

/**
* A mapping from (uppercased) String labels identifying time units,
* to the corresponding {@link ChronoUnit} enum (e.g. "YEARS") used to
* to the corresponding {@link ChronoUnit} value (e.g. "YEARS") used to
* set/add/roll that unit of measurement.
*
* <p>
* A single logical unit of time might be represented by multiple labels
* for convenience (ie: <code>DATE==DAYS</code>,
* for convenience (i.e. <code>DATE==DAYS</code>,
* <code>MILLI==MILLIS</code>)
* </p>
*
Expand Down Expand Up @@ -220,6 +225,7 @@ private static LocalDateTime round(LocalDateTime t, String unit) {
*
* @param now an optional fixed date to use as "NOW"
* @param val the string to parse
* @return result of applying the parsed expression to "NOW".
* @throws Exception
*/
public static Date parseMath(Date now, String val) throws Exception {
Expand Down Expand Up @@ -308,6 +314,7 @@ public TimeZone getTimeZone() {
/**
* Defines this instance's concept of "now".
*
* @param n new value of "now".
* @see #getNow
*/
public void setNow(Date n) {
Expand All @@ -316,12 +323,12 @@ public void setNow(Date n) {

/**
* Returns a clone of this instance's concept of "now" (never null).
*
* If setNow was never called (or if null was specified) then this method
* first defines 'now' as the value dictated by the SolrRequestInfo if it
* exists -- otherwise it uses a new Date instance at the moment getNow()
* is first called.
*
* @return "now".
* @see #setNow
* @see SolrRequestInfo#getNOW
*/
Expand All @@ -334,16 +341,21 @@ public Date getNow() {
}

/**
* Parses a string of commands relative "now" are returns the resulting Date.
* Parses a date expression relative to "now".
*
* @throws ParseException positions in ParseExceptions are token positions, not character positions.
* @param math a date expression such as "+24MONTHS".
* @return the result of applying the expression to the current time.
* @throws ParseException positions in ParseExceptions are token positions,
* not character positions.
*/
public Date parseMath(String math) throws ParseException {
/* check for No-Op */
if (0 == math.length()) {
return getNow();
}

LOG.debug("parsing {}", math);

ZoneId zoneId = zone.toZoneId();
// localDateTime is a date and time local to the timezone specified
LocalDateTime localDateTime = ZonedDateTime.ofInstant(getNow().toInstant(), zoneId).toLocalDateTime();
Expand Down Expand Up @@ -394,11 +406,44 @@ public Date parseMath(String math) throws ParseException {
}
}

LOG.debug("returning {}", localDateTime);
return Date.from(ZonedDateTime.of(localDateTime, zoneId).toInstant());
}

private static Pattern splitter = Pattern.compile("\\b|(?<=\\d)(?=\\D)");

/**
* For manual testing. With one argument, test one-argument parseMath.
* With two (or more) arguments, test two-argument parseMath.
*
* @param argv date math expressions.
* @throws java.lang.Exception passed through.
*/
public static void main(String[] argv)
throws Exception {
DateMathParser parser = new DateMathParser();
try {
Date parsed;

if (argv.length <= 0) {
System.err.println("Date math expression(s) expected.");
}

if (argv.length > 0) {
parsed = parser.parseMath(argv[0]);
System.out.format("Applied %s to implicit current time: %s%n",
argv[0], parsed.toString());
}

if (argv.length > 1) {
parsed = DateMathParser.parseMath(new Date(), argv[1]);
System.out.format("Applied %s to explicit current time: %s%n",
argv[1], parsed.toString());
}
} catch (ParseException ex) {
System.err.format("Oops: %s%n", ex.getMessage());
}
}
}


42 changes: 42 additions & 0 deletions dspace-api/src/main/java/org/dspace/util/TimeHelpers.java
@@ -0,0 +1,42 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.util;

import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;

/**
* Various manipulations of dates and times.
*
* @author mwood
*/
public class TimeHelpers {
private static final TimeZone UTC = TimeZone.getTimeZone("UTC");

/**
* Never instantiate this class.
*/
private TimeHelpers() {}

/**
* Set a Date's time to midnight UTC.
*
* @param from some date-time.
* @return midnight UTC of the supplied date-time.
*/
public static Date toMidnightUTC(Date from) {
GregorianCalendar calendar = new GregorianCalendar(UTC);
calendar.setTime(from);
calendar.set(GregorianCalendar.HOUR_OF_DAY, 0);
calendar.set(GregorianCalendar.MINUTE, 0);
calendar.set(GregorianCalendar.SECOND, 0);
calendar.set(GregorianCalendar.MILLISECOND, 0);
return calendar.getTime();
}
}
34 changes: 34 additions & 0 deletions dspace-api/src/test/java/org/dspace/util/TimeHelpersTest.java
@@ -0,0 +1,34 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.util;

import static org.junit.Assert.assertEquals;

import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Date;

import org.junit.Test;

/**
* Test {@link TimeHelpers}.
* @author Mark H. Wood <mwood@iupui.edu>
*/
public class TimeHelpersTest {
/**
* Test of toMidnightUTC method, of class TimeHelpers.
*/
@Test
public void testToMidnightUTC() {
System.out.println("toMidnightUTC");
Date from = Date.from(ZonedDateTime.of(1957, 01, 27, 04, 05, 06, 007, ZoneOffset.UTC).toInstant());
Date expResult = Date.from(ZonedDateTime.of(1957, 01, 27, 00, 00, 00, 000, ZoneOffset.UTC).toInstant());
Date result = TimeHelpers.toMidnightUTC(from);
assertEquals(expResult, result);
}
}

0 comments on commit 894bc39

Please sign in to comment.