Time zone fix

- removed unused dependencies
- matched joda to entire monasca
- switched to standard java date + added explicit UTC

Change-Id: I04e986097010792aed772e83e529edb6d88a2125
This commit is contained in:
Tomasz Trębski 2015-09-04 09:51:28 +02:00
parent df99b11a40
commit fc30e21e5c
10 changed files with 254 additions and 86 deletions

View File

@ -30,7 +30,7 @@ A change has been submitted to StackForge to switch to bare-trusty for this buil
* [monasca-common-streaming](https://github.com/stackforge/monasca-common/tree/master/java/monasca-common-streaming) - Streaming related utilities.
* [monasca-common-testing](https://github.com/stackforge/monasca-common/tree/master/java/monasca-common-testing) - A set of testing related dependencies.
* [monasca-common-util](https://github.com/stackforge/monasca-common/tree/master/java/monasca-common-util) - Various utilities such as for serialization, dependency injection, date and time, invocation retries, concurrency, etc.
* [monasca-common-hibernate](https://github.com/stackforge/monasca-common/tree/master/java/monasca-common-hibernate) - Hibernate based model of Monasca SQL schema
python monasca-common
======================

View File

@ -10,9 +10,7 @@
<packaging>jar</packaging>
<properties>
<hibernate-core.version>4.3.10.Final</hibernate-core.version>
<usertype.core.version>3.2.0.GA</usertype.core.version>
<joda-time.version>2.8.1</joda-time.version>
<hibernate-core.version>5.0.1.Final</hibernate-core.version>
</properties>
<dependencies>
@ -20,27 +18,12 @@
<groupId>monasca-common</groupId>
<artifactId>monasca-common-model</artifactId>
<version>${project.version}</version>
<exclusions>
<exclusion>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate-core.version}</version>
</dependency>
<dependency>
<groupId>org.jadira.usertype</groupId>
<artifactId>usertype.core</artifactId>
<version>${usertype.core.version}</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>${joda-time.version}</version>
</dependency>
</dependencies>
</project>

View File

@ -16,7 +16,9 @@
package monasca.common.hibernate.db;
import java.io.Serializable;
import java.util.Date;
import javax.annotation.Nullable;
import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import javax.persistence.Version;
@ -24,43 +26,38 @@ import javax.persistence.Version;
import com.google.common.base.Objects;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
import org.hibernate.annotations.Parameter;
import org.hibernate.annotations.Type;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import monasca.common.hibernate.core.AuditablePersistable;
/**
* <b>Abstract</b> implementation for {@link AuditablePersistable}.
*
* Defines auditable information such as:
* <ol>
* <li>{@link #createdAt} - creation date for entity</li>
* <li>{@link #updatedAt} - last update date for entity</li>
* </ol>
*
* @param <T> primary key type
*
* @see AbstractPersistable
*/
@DynamicInsert
@DynamicUpdate
@MappedSuperclass
abstract class AbstractAuditablePersistable<T extends Serializable>
extends AbstractPersistable<T>
implements AuditablePersistable<T> {
static final String DATE_TIME_TYPE = "org.jadira.usertype.dateandtime.joda.PersistentDateTime";
static final String DB_ZONE = "UTC";
static final String JAVA_ZONE = "jvm";
private static final long serialVersionUID = 2335373173379564615L;
@Column(name = "created_at", nullable = false)
@Type(
type = DATE_TIME_TYPE,
parameters = {
@Parameter(name = "databaseZone", value = DB_ZONE),
@Parameter(name = "javaZone", value = JAVA_ZONE)
}
)
private DateTime createdAt;
private Date createdAt;
@Version
@Column(name = "updated_at", nullable = false)
@Type(
type = DATE_TIME_TYPE,
parameters = {
@Parameter(name = "databaseZone", value = DB_ZONE),
@Parameter(name = "javaZone", value = JAVA_ZONE)
}
)
private DateTime updatedAt;
private Date updatedAt;
AbstractAuditablePersistable() {
this(null, null, null);
@ -77,47 +74,79 @@ abstract class AbstractAuditablePersistable<T extends Serializable>
this.setDates(createdAt, updatedAt);
}
/**
* Null-safe method that transform {@link DateTime} into plain {@link java.util.Date}
*
* @param value DateTime instance
*
* @return plain Date
*
* @see #nullSafeGetDate(Date)
*/
static Date nullSafeSetDate(@Nullable final DateTime value) {
if (value == null) {
return null;
}
return value.toDateTime(DateTimeZone.UTC).toDate();
}
/**
* Null-safe method that transform {@link Date} into plain {@link DateTime}
*
* @param value Date instance
*
* @return DateTime
*
* @see #nullSafeSetDate(DateTime)
*/
static DateTime nullSafeGetDate(@Nullable final Date value) {
if (value == null) {
return null;
}
return new DateTime(value.getTime(), DateTimeZone.UTC);
}
@Override
public DateTime getCreatedAt() {
return createdAt;
return nullSafeGetDate(this.createdAt);
}
@Override
public AuditablePersistable<T> setCreatedAt(DateTime createdAt) {
this.createdAt = createdAt;
this.createdAt = nullSafeSetDate(createdAt);
return this;
}
@Override
public DateTime getUpdatedAt() {
return updatedAt;
return nullSafeGetDate(this.updatedAt);
}
@Override
public AuditablePersistable<T> setUpdatedAt(DateTime updatedAt) {
this.updatedAt = updatedAt;
this.updatedAt = nullSafeSetDate(updatedAt);
return this;
}
/**
* Ensures that both {@link #createdAt} and {@link #updatedAt} will be
* set to the earliest possible value in case passssed values are {@code NULL}
* set to the earliest possible value in case passed values are {@code NULL}
*
* @param createdAt created date
* @param updatedAt updated date
*/
private void setDates(final DateTime createdAt,
final DateTime updatedAt) {
if (createdAt == null && updatedAt == null) {
this.updatedAt = DateTime.now();
this.createdAt = DateTime.now();
} else if (createdAt == null) {
this.createdAt = DateTime.now();
} else if (updatedAt == null) {
this.updatedAt = DateTime.now();
final Date date = DateTime.now(DateTimeZone.UTC).toDate();
if (createdAt == null) {
this.createdAt = date;
} else {
this.createdAt = createdAt;
this.updatedAt = updatedAt;
this.createdAt = createdAt.toDateTime(DateTimeZone.UTC).toDate();
}
if (updatedAt == null) {
this.updatedAt = date;
} else {
this.updatedAt = updatedAt.toDateTime(DateTimeZone.UTC).toDate();
}
}

View File

@ -27,6 +27,17 @@ import org.hibernate.annotations.DynamicUpdate;
import monasca.common.hibernate.core.Persistable;
/**
* <b>Abstract</b> implementation for {@link Persistable}.
*
* Defines primary key of a specific entity.
* Primary key may take any arbitrary type that
* is required by specific case.
*
* @param <T> primary key type
*
* @see AbstractAuditablePersistable
*/
@DynamicInsert
@DynamicUpdate
@MappedSuperclass

View File

@ -15,6 +15,7 @@
package monasca.common.hibernate.db;
import java.util.Collection;
import java.util.Date;
import javax.annotation.Nullable;
import javax.persistence.CascadeType;
@ -35,8 +36,6 @@ import com.google.common.collect.FluentIterable;
import com.google.common.collect.Sets;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
import org.hibernate.annotations.Parameter;
import org.hibernate.annotations.Type;
import org.joda.time.DateTime;
import monasca.common.model.alarm.AlarmState;
@ -76,14 +75,7 @@ public class AlarmDb
private String link;
@Column(name = "state_updated_at")
@Type(
type = DATE_TIME_TYPE,
parameters = {
@Parameter(name = "databaseZone", value = DB_ZONE),
@Parameter(name = "javaZone", value = JAVA_ZONE)
}
)
private DateTime stateUpdatedAt;
private Date stateUpdatedAt;
@OneToMany(mappedBy = "alarmMetricId.alarm", fetch = FetchType.LAZY, cascade = {
CascadeType.PERSIST,
@ -119,7 +111,7 @@ public class AlarmDb
this.link = link;
this.state = state;
this.lifecycleState = lifecycleState;
this.stateUpdatedAt = stateUpdatedAt;
this.setStateUpdatedAt(stateUpdatedAt);
}
public AlarmState getState() {
@ -150,11 +142,11 @@ public class AlarmDb
}
public DateTime getStateUpdatedAt() {
return stateUpdatedAt;
return nullSafeGetDate(this.stateUpdatedAt);
}
public AlarmDb setStateUpdatedAt(DateTime stateUpdatedAt) {
this.stateUpdatedAt = stateUpdatedAt;
this.stateUpdatedAt = nullSafeSetDate(stateUpdatedAt);
return this;
}

View File

@ -16,6 +16,7 @@ package monasca.common.hibernate.db;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import javax.annotation.Nullable;
import javax.persistence.Basic;
@ -36,7 +37,6 @@ import com.google.common.collect.FluentIterable;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.hibernate.annotations.BatchSize;
import org.hibernate.annotations.Parameter;
import org.hibernate.annotations.Type;
import org.joda.time.DateTime;
@ -95,14 +95,7 @@ public class AlarmDefinitionDb
private boolean actionsEnabled = DEFAULT_ACTIONS_ENABLED;
@Column(name = "deleted_at")
@Type(
type = DATE_TIME_TYPE,
parameters = {
@Parameter(name = "databaseZone", value = DB_ZONE),
@Parameter(name = "javaZone", value = JAVA_ZONE)
}
)
private DateTime deletedAt;
private Date deletedAt;
@BatchSize(size = 50)
@OneToMany(mappedBy = "alarmDefinition", fetch = FetchType.LAZY)
@ -132,7 +125,7 @@ public class AlarmDefinitionDb
this.severity = severity;
this.matchBy = matchBy;
this.actionsEnabled = actionsEnabled;
this.deletedAt = deletedAt;
this.setDeletedAt(deletedAt);
}
public AlarmDefinitionDb(String id,
@ -145,7 +138,7 @@ public class AlarmDefinitionDb
}
public AlarmDefinitionDb setDeletedAt(final DateTime deletedAt) {
this.deletedAt = deletedAt;
this.deletedAt = nullSafeSetDate(deletedAt);
return this;
}
@ -220,7 +213,7 @@ public class AlarmDefinitionDb
}
public DateTime getDeletedAt() {
return deletedAt;
return nullSafeGetDate(this.deletedAt);
}
public boolean hasAlarm(final AlarmDb alarm) {

View File

@ -24,11 +24,12 @@ import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import monasca.common.model.alarm.AlarmOperator;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
import org.joda.time.DateTime;
import monasca.common.model.alarm.AlarmOperator;
@Entity
@Table(name = "sub_alarm_definition")
@NamedQueries({

View File

@ -84,7 +84,7 @@
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>4.3.0.Final</version>
<version>5.2.1.Final</version>
</dependency>
<!-- Test dependencies -->

View File

@ -16,7 +16,17 @@
*/
package monasca.common.util;
import java.util.Arrays;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
public final class Conversions {
private static final String[] SUPPORTED_VARIANT_TO_ENUM_TYPES = new String[]{
String.class.getSimpleName(),
Number.class.getSimpleName(),
Enum.class.getSimpleName()
};
/**
* Converts a Java Object of type Number to an Integer
@ -32,4 +42,91 @@ public final class Conversions {
variant.getClass(), variant));
}
}
}
/**
* Converts a Java Object to DateTime instance
*
* @param variant object of type supported in {@link org.joda.time.convert.ConverterManager}
*
* @return DateTime in {@link DateTimeZone#UTC}
*
* @throws IllegalArgumentException
* @see #variantToDateTime(Object, DateTimeZone)
* @see DateTime
* @see DateTimeZone#UTC
*/
public static DateTime variantToDateTime(final Object variant) {
return variantToDateTime(variant, DateTimeZone.UTC);
}
/**
* Converts a Java Object to DateTime instance using given {@code timeZone}
*
* @param variant object of type supported in {@link org.joda.time.convert.ConverterManager}
* @param timeZone timeZone to be used
*
* @return DateTime in {@code timeZone}
*
* @throws IllegalArgumentException
* @see #variantToDateTime(Object)
* @see DateTime
* @see DateTimeZone
*/
public static DateTime variantToDateTime(final Object variant, final DateTimeZone timeZone) {
if (variant instanceof DateTime) {
return ((DateTime) variant).toDateTime(timeZone);
}
return new DateTime(variant, timeZone);
}
/**
* Converts variant to {@code enumClazz} instance.
*
* Supported variants are:
* <ol>
* <li>{@link String}, trimmed and upper-cased</li>
* <li>{@link Number}, taken from {@link Class#getEnumConstants}</li>
* <li>{@link Enum}, simple cast</li>
* </ol>
*
* @param variant object of type supported by this method, see above
* @param enumClazz desired {@link Enum}
* @param <T> enumType of {@code enumClazz}
*
* @return valid enum class instance
*
* @throws IllegalArgumentException
*/
@SuppressWarnings("unchecked")
public static <T extends Enum<T>> T variantToEnum(final Object variant, final Class<T> enumClazz) {
if (variant == null) {
return null;
}
if (variant instanceof String) {
return Enum.valueOf(enumClazz, ((String) variant).trim().toUpperCase());
} else if (variant instanceof Number) {
final Integer index = variantToInteger(variant);
final T[] enumConstants = enumClazz.getEnumConstants();
if (index < 0 || index >= enumConstants.length) {
throw new IllegalArgumentException(
String.format("Variant of type \"%s\", and value \"%s\" is out of range [, %d]",
variant.getClass(),
variant,
enumConstants.length
)
);
}
return enumConstants[index];
} else if (variant instanceof Enum) {
return (T) variant;
}
throw new IllegalArgumentException(String.format("\"%s\", and value \"%s\" is not one of %s",
variant.getClass(), variant, Arrays.toString(SUPPORTED_VARIANT_TO_ENUM_TYPES)));
}
}

View File

@ -14,12 +14,15 @@
package monasca.common.util;
import static org.testng.Assert.assertEquals;
import org.testng.annotations.Test;
import static org.testng.Assert.assertNotEquals;
import java.math.BigDecimal;
import com.beust.jcommander.internal.Lists;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.testng.annotations.Test;
@Test
public class ConversionsTest {
public void testInteger() {
@ -49,4 +52,63 @@ public class ConversionsTest {
public void testObject() {
Conversions.variantToInteger(new Object());
}
public void testDateTimeShouldNotEqualDifferentTZExplicit() {
final DateTime now = DateTime.now(DateTimeZone.UTC);
assertNotEquals(now, Conversions.variantToDateTime(now, DateTimeZone.forOffsetHours(2)));
}
public void testDateTimeShouldNotEqualDifferentTZImplicit() {
final DateTime now = DateTime.now();
assertNotEquals(now, Conversions.variantToDateTime(now));
}
public void testDateTimeShouldEqualSameTZImplicit() {
final DateTime now = DateTime.now(DateTimeZone.UTC);
assertEquals(now, Conversions.variantToDateTime(now));
}
public void testDateTimeShouldEqualSameTZExplicit() {
final DateTime now = DateTime.now(DateTimeZone.UTC);
assertEquals(now, Conversions.variantToDateTime(now, DateTimeZone.UTC));
}
public void testEnumFromString() {
assertEquals(MockEnum.THIS, Conversions.variantToEnum("THIS", MockEnum.class));
}
public void testEnumFromStringLowerCased() {
assertEquals(MockEnum.THIS, Conversions.variantToEnum("this", MockEnum.class));
}
public void testEnumFromStringWithSpaces() {
assertEquals(MockEnum.THIS, Conversions.variantToEnum(" THIS ", MockEnum.class));
}
public void testEnumFromNumber() {
assertEquals(MockEnum.IS, Conversions.variantToEnum(1, MockEnum.class));
}
public void testEnumFromNumberDouble() {
assertEquals(MockEnum.IS, Conversions.variantToEnum(1.0, MockEnum.class));
}
public void testEnumFromEnum() {
assertEquals(MockEnum.TEST, Conversions.variantToEnum(MockEnum.TEST, MockEnum.class));
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testEnumShouldFailUnsupportedType(){
Conversions.variantToEnum(Lists.newArrayList(),MockEnum.class);
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testEnumShouldFailInvalidEnumIndex(){
final int invalidIndex = MockEnum.class.getEnumConstants().length + 1;
Conversions.variantToEnum(invalidIndex, MockEnum.class);
}
private enum MockEnum {
THIS,IS,TEST
}
}