Данные Deltaspike (CDI + JPA) Пользовательские PrePersistAuditListener и PreUpdateAuditListener для пользовательских java.time.ChronoLocalDateTime в UTC

Deltaspike Ver 1.7.2 на Wildfly 9.0.2. Финал

Я использую JPA @EntityListeners(AuditEntityListener.class) и функциональность аудита Deltaspike Data (@CreatedOn, @ModifiedOn а также @ModifiedBy аннотации) на компоненте с той разницей, что у меня есть пользовательская реализация java.time.ChronoLocalDateTime который преобразует любой LocalDateTime + ZoneOffset ИЛИ ZonedDateTime быть датой и временем UTC.

UTCDateTime

public class UTCDateTime implements ChronoLocalDateTime<LocalDate>, Serializable {

  private static final long serialVersionUID = 6492792765662073566L;
  private static final ZoneOffset UTC = ZoneOffset.UTC;
  private final LocalDateTime datetime;

  // a lot of other details left out
}

Части объекта EJB

@MappedSuperclass
public class InsertableAuditableBean extends BaseBean implements InsertableAuditable {

  @NotNull
  @Size(min = 1, max = 50)
  @Column(name = "zz_inserted_by", length = 50, nullable = false)
  private String insertedBy;

  @CreatedOn
  @NotNull
  @Temporal(value = TemporalType.TIMESTAMP)
  @Column(name = "zz_inserted_on", nullable = false)
  private UTCDateTime insertedOn;

  // getters & setters
}

@MappedSuperclass
public class UpdateableAuditableBean extends InsertableAuditableBean implements UpdateableAuditable {

  @ModifiedBy
  @Size(min = 1, max = 50)
  @Column(name = "zz_updated_by", length = 50, nullable = true)
  private String updatedBy;

  @ModifiedOn
  @Temporal(value = TemporalType.TIMESTAMP)
  @Column(name = "zz_updated_on", nullable = true)
  private UTCDateTime updatedOn;

  // getters & setters
}

@Entity
@EntityListeners(AuditEntityListener.class)
@Table(schema = "data", name = "manufacturer", uniqueConstraints = {
    @UniqueConstraint(columnNames = { "man_name", "man_country" })
})
@AttributeOverrides({
    @AttributeOverride(name = "primaryKey", column = @Column(name = "man_serial")),
    @AttributeOverride(name = "insertedBy", column = @Column(name = "man_inserted_by")),
    @AttributeOverride(name = "insertedOn", column = @Column(name = "man_inserted_on")),
    @AttributeOverride(name = "updatedBy", column = @Column(name = "man_updated_by")),
    @AttributeOverride(name = "updatedOn", column = @Column(name = "man_updated_on"))
})
@SequenceGenerator(name = "default_seq", schema = "data", sequenceName = "manufacturer_man_serial_seq",
    allocationSize = 1)
public class Manufacturer extends MirroredUpdateableAuditableBean implements IManufacturer {
  // nothing special here
}

Существует также пользовательский AttributeConverter для класса UTCDateTime, поскольку значение периода сохраняется в базе данных.

@Converter(autoApply = true)
public class UTCDateTimePersistenceConverter implements AttributeConverter<UTCDateTime, Long> {

  @Override
  public Long convertToDatabaseColumn(final UTCDateTime entityValue) {
    Long res = null;
    if (entityValue != null) {
      res = entityValue.toMillis();
    }
    return res;
  }

  @Override
  public UTCDateTime convertToEntityAttribute(final Long databaseValue) {
    UTCDateTime res = null;
    if (databaseValue != null) {
      res = new UTCDateTime(Instant.ofEpochMilli(databaseValue));
    }
    return res;
  }
}

Теперь, когда я сохраняю сущность, я получаю следующее исключение (последний бит с реальной причиной):

Caused by: org.apache.deltaspike.data.api.QueryInvocationException: Failed calling Repository: [Repository=systems.apace.data.manufacturer.model.dao.ManufacturerDAO,entity=systems.apace.data.manufacturer.model.Manufacturer,method=persist,exception=class java.lang.reflect.InvocationTargetException,message=null
        at systems.apace.data.manufacturer.services.ManufacturerServiceBeanIntegrationTest.testInsertBean(ManufacturerServiceBeanIntegrationTest.java:55)
Caused by: java.lang.reflect.InvocationTargetException
        at systems.apace.data.manufacturer.services.ManufacturerServiceBeanIntegrationTest.testInsertBean(ManufacturerServiceBeanIntegrationTest.java:55)
Caused by: org.apache.deltaspike.data.impl.audit.AuditPropertyException: Failed to set property Manufacturer.insertedOn, is this a temporal type?
        at systems.apace.data.manufacturer.services.ManufacturerServiceBeanIntegrationTest.testInsertBean(ManufacturerServiceBeanIntegrationTest.java:55)
Caused by: java.lang.IllegalArgumentException: Annotated field is not a date class: class za.co.t9.common.utils.time.UTCDateTime
        at systems.apace.data.manufacturer.services.ManufacturerServiceBeanIntegrationTest.testInsertBean(ManufacturerServiceBeanIntegrationTest.java:55)

Есть ли способ реализовать свой собственный org.apache.deltaspike.data.impl.audit.PrePersistAuditListener а также org.apache.deltaspike.data.impl.audit.PreUpdateAuditListener и использовать их для создания экземпляра UTCDateTime?

Было бы правильно написать свой собственный EntityListener -> UTCDateTimeAuditListener и использовать это @EntityListeners(UTCDateTimeAuditEntityListener.class) где UTCDateTimeAuditListener следует за org.apache.deltaspike.data.impl.audit.AuditEntityListener подход?

Во-вторых, мне нужно где-нибудь использовать CDI Qualifier, чтобы убедиться, что мой UTCDateTimeAuditEntityListener получает ссылку на правильный PrePersistAuditListener а также PreUpdateAuditListener реализации, которые знают, как построить UTCDateTime пример?

Наконец, я не знаю, насколько это актуально, но где org.apache.deltaspike.data.impl.audit.TimestampsProvider вписаться в этот сценарий?

Обновить

Я добился определенного прогресса в ответах на мои вопросы. Что я сделал до сих пор. Там нет необходимости для кастома AuditEntityListener достаточно того, что поставляет DeltaSpike.

Я создал новую реализацию TimestampsProvider который способен справиться с моим UTCDateTime объекты.

@Alternative
@Dependent
public class UTCDateTimeAuditProvider implements PrePersistAuditListener, PreUpdateAuditListener {
  // implementation details not important
}

Bean.xml также перечисляет это как CDI @Alternative

<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
       version="1.1" bean-discovery-mode="annotated">

  <alternatives>
    <class>systems.apace.data.manufacturer.model.UTCDateTimeAuditProvider</class>
  </alternatives>
</beans>

Содержание apache-deltaspike.properties

globalAlternatives.org.apache.deltaspike.jpa.spi.transaction.TransactionStrategy=org.apache.deltaspike.jpa.impl.transaction.ContainerManagedTransactionStrategy

Проблема в том, что оба AuditProvider экземпляры выполняются, сначала моя реализация (UTCDateTimeAuditProvider), а затем по умолчанию TimestampsProvider

2016-12-13 01:29:22,929 INFO  Apace-BPS s.a.d.m.m.UTCDateTimeAuditProvider [ServerService Thread Pool -- 319] [Unknown Id] - prePersist: class systems.apace.data.manufacturer.model.UTCDateTimeAuditProvider
2016-12-13 01:29:22,931 INFO  Apace-BPS s.a.d.m.m.UTCDateTimeAuditProvider [ServerService Thread Pool -- 319] [Unknown Id] - Updated property Manufacturer.insertedOn with 2016-12-12T23:29:22.930
2016-12-13 01:29:22,932 INFO  Apace-BPS z.c.t.c.b.e.EntityManagerProducer [ServerService Thread Pool -- 319] [Unknown Id] - username of Principal: anonymous
2016-12-13 01:29:22,932 FINER [org.apache.deltaspike.data.impl.audit.AuditProvider] (ServerService Thread Pool -- 319) Updated Manufacturer.updatedBy with anonymous
2016-12-13 01:29:22,935 ERROR [org.jboss.msc.service.fail] (ServerService Thread Pool -- 319) MSC000001: Failed to start service jboss.deployment.subunit."bpsserver.ear"."systems-apace-data-model-1.0-SNAPSHOT.jar".component.ManufacturerSi
ngleton.START: org.jboss.msc.service.StartException in service jboss.deployment.subunit."bpsserver.ear"."systems-apace-data-model-1.0-SNAPSHOT.jar".component.ManufacturerSingleton.START: java.lang.IllegalStateException: WFLYEE0042: Failed
 to construct component instance
        at org.jboss.as.ee.component.ComponentStartService$1.run(ComponentStartService.java:57)
...
Caused by: org.apache.deltaspike.data.impl.audit.AuditPropertyException: Failed to set property Manufacturer.insertedOn, is this a temporal type?
        at org.apache.deltaspike.data.impl.audit.TimestampsProvider.setProperty(TimestampsProvider.java:86)
        at org.apache.deltaspike.data.impl.audit.TimestampsProvider.updateTimestamps(TimestampsProvider.java:67)
        at org.apache.deltaspike.data.impl.audit.TimestampsProvider.prePersist(TimestampsProvider.java:43)
        at org.apache.deltaspike.data.impl.audit.AuditEntityListener.persist(AuditEntityListener.java:42)
...
Caused by: java.lang.IllegalArgumentException: Annotated field is not a date class: class za.co.t9.common.utils.time.UTCDateTime
        at org.apache.deltaspike.data.impl.audit.TimestampsProvider.now(TimestampsProvider.java:115)
        at org.apache.deltaspike.data.impl.audit.TimestampsProvider.setProperty(TimestampsProvider.java:79)
        ... 115 more

Я пробовал различные комбинации указания моей реализации @Alternative только с аннотацией или только в beans.xml и в классе и в beans.xml.

Кто-нибудь знает, почему по умолчанию TimestampsProvider исполняется после моей реализации @Alternative?

Deltaspike Ver 1.7.2 на Wildfly 9.0.2. Финал со сваркой 2.2.16 (SP1)

1 ответ

Аудит данных Deltaspike не поддерживает API даты и времени Java 8. Вы можете объединить методы @PrePersist, @PreUpdate с полем Deltaspike @ModifiedBy в одного общего предка для всех проверяемых объектов (преимущество: нет необходимости в специальном способе получения имени субъекта).

Другие вопросы по тегам