/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.isis.objectstore.jdo.applib.service; import java.sql.Timestamp; import java.util.Comparator; import java.util.UUID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.isis.applib.annotation.Action; import org.apache.isis.applib.annotation.ActionLayout; import org.apache.isis.applib.annotation.DomainObject; import org.apache.isis.applib.annotation.DomainObjectLayout; import org.apache.isis.applib.annotation.Editing; import org.apache.isis.applib.annotation.MemberGroupLayout; import org.apache.isis.applib.annotation.MemberOrder; import org.apache.isis.applib.annotation.Programmatic; import org.apache.isis.applib.annotation.Property; import org.apache.isis.applib.annotation.PropertyLayout; import org.apache.isis.applib.annotation.SemanticsOf; import org.apache.isis.applib.annotation.Where; import org.apache.isis.applib.services.bookmark.Bookmark; import org.apache.isis.applib.services.bookmark.BookmarkService2; import org.apache.isis.applib.services.message.MessageService; import org.apache.isis.applib.services.metamodel.MetaModelService2; import org.apache.isis.applib.services.metamodel.MetaModelService3; import org.apache.isis.applib.services.publish.PublisherService; import org.apache.isis.applib.services.publish.PublishingService; import org.apache.isis.applib.util.ObjectContracts; import static org.apache.isis.applib.annotation.Optionality.MANDATORY; import static org.apache.isis.applib.annotation.Optionality.OPTIONAL; /** * An abstraction of some sort of recorded change to a domain object, either a <tt>CommandJdo</tt>, an * <tt>AuditEntryJdo</tt> or a <tt>PublishedEventJdo</tt>. */ @MemberGroupLayout( columnSpans={6,0,6,12}, left={"Identifiers"}, right={"Target","Detail"}) @DomainObjectLayout( named="Domain Change" ) @DomainObject( editing = Editing.DISABLED ) public abstract class DomainChangeJdoAbstract { @SuppressWarnings("unused") private static final Logger LOG = LoggerFactory.getLogger(DomainChangeJdoAbstract.class); public static enum ChangeType { COMMAND, AUDIT_ENTRY, /** * As per {@link PublishingService}. * * @deprecated - replaced by {@link #PUBLISHED_INTERACTION} (because {@link PublishingService} has been replaced by {@link PublisherService}). */ @Deprecated PUBLISHED_EVENT, /** * As per {@link PublisherService}. */ PUBLISHED_INTERACTION; @Override public String toString() { return name().replace("_", " "); } } public DomainChangeJdoAbstract(final ChangeType changeType) { this.type = changeType; } private final ChangeType type; /** * Distinguishes commands from audit entries from published events/interactions (when these are shown mixed together in a (standalone) table). */ @Property @PropertyLayout( hidden = Where.ALL_EXCEPT_STANDALONE_TABLES ) @MemberOrder(name="Identifiers", sequence = "1") public ChangeType getType() { return type; } // ////////////////////////////////////// /** * The user that caused the change. * * <p> * This dummy implementation is a trick so that Isis will render the property in a standalone table. Each of the * subclasses override with the "real" implementation. */ @Property @MemberOrder(name="Identifiers", sequence = "10") public String getUser() { return null; } // ////////////////////////////////////// /** * The time that the change occurred. * * <p> * This dummy implementation is a trick so that Isis will render the property in a standalone table. Each of the * subclasses override with the "real" implementation. */ @Property @MemberOrder(name="Identifiers", sequence = "20") public Timestamp getTimestamp() { return null; } // ////////////////////////////////////// /** * The unique identifier (a GUID) of the transaction in which this change occurred. * * <p> * This dummy implementation is a trick so that Isis will render the property in a standalone table. Each of the * subclasses override with the "real" implementation. */ @Property @MemberOrder(name="Identifiers",sequence = "50") public UUID getTransactionId() { return null; } // ////////////////////////////////////// /** * The class of the domain object being changed. * * <p> * This dummy implementation is a trick so that Isis will render the property in a standalone table. Each of the * subclasses override with the "real" implementation. */ @Property @PropertyLayout(named="Class") @MemberOrder(name="Target", sequence = "10") public String getTargetClass() { return null; } // ////////////////////////////////////// @Programmatic public Bookmark getTarget() { final String str = getTargetStr(); return str != null? new Bookmark(str): null; } @Programmatic public void setTarget(Bookmark target) { final String targetStr = target != null ? target.toString() : null; setTargetStr(targetStr); } // ////////////////////////////////////// private String targetAction; /** * The member interaction (ie action invocation or property edit) which caused the domain object to be changed. * * <p> * Populated for commands and for published events that represent action invocations or property edits. * </p> * * <p> * This dummy implementation is a trick so that Isis will render the property in a standalone table. Each of the * subclasses override with the "real" implementation. * </p> * * <p> * NB: commands and published events applied only to actions, hence the name of this field. In a future release * the name of this field may change to "TargetMember". Note that the {@link PropertyLayout} already uses * "Member" this as a name hint. * </p> * */ @Property( optionality = OPTIONAL ) @PropertyLayout( named="Member", hidden = Where.ALL_EXCEPT_STANDALONE_TABLES ) @MemberOrder(name="Target", sequence = "20") public String getTargetAction() { return targetAction; } // ////////////////////////////////////// /** * The (string representation of the) {@link Bookmark} identifying the domain object that has changed. * * <p> * This dummy implementation is a trick so that Isis will render the property in a standalone table. Each of the * subclasses override with the "real" implementation. */ @Property @PropertyLayout(named="Object") @MemberOrder(name="Target", sequence="30") public String getTargetStr() { return null; } /** * For {@link #setTarget(Bookmark)} to delegate to. */ public abstract void setTargetStr(final String targetStr); // ////////////////////////////////////// @Action( semantics = SemanticsOf.SAFE ) @ActionLayout( named = "Open" ) @MemberOrder(name="TargetStr", sequence="1") public Object openTargetObject() { try { return bookmarkService != null ? bookmarkService.lookup(getTarget(), BookmarkService2.FieldResetPolicy.DONT_RESET) : null; } catch(RuntimeException ex) { if(ex.getClass().getName().contains("ObjectNotFoundException")) { messageService.warnUser("Object not found - has it since been deleted?"); return null; } throw ex; } } public boolean hideOpenTargetObject() { return getTarget() == null; } public String disableOpenTargetObject() { final Object targetObject = getTarget(); if (targetObject == null) { return null; } final MetaModelService2.Sort sortOfObject = metaModelService.sortOf(getTarget(), MetaModelService3.Mode.RELAXED); return !(sortOfObject.isViewModel() || sortOfObject.isJdoEntity()) ? "Can only open view models or entities" : null; } // ////////////////////////////////////// /** * The property of the object that was changed. * * <p> * Populated only for <tt>AuditEntryJdo</tt>. * * <p> * This dummy implementation is a trick so that Isis will render the property in a standalone table. Each of the * subclasses override with the "real" implementation. */ @Property( optionality = OPTIONAL ) @PropertyLayout( hidden = Where.ALL_EXCEPT_STANDALONE_TABLES ) @MemberOrder(name="Target",sequence = "21") public String getPropertyId() { return null; } // ////////////////////////////////////// /** * The value of the property prior to it being changed. * * <p> * Populated only for <tt>AuditEntryJdo</tt>. * * <p> * This dummy implementation is a trick so that Isis will render the property in a standalone table. Each of the * subclasses override with the "real" implementation. */ @Property( optionality = OPTIONAL ) @PropertyLayout( hidden = Where.ALL_EXCEPT_STANDALONE_TABLES ) @MemberOrder(name="Detail",sequence = "6") public String getPreValue() { return null; } // ////////////////////////////////////// /** * The value of the property after it has changed. * * <p> * Populated only for <tt>AuditEntryJdo</tt>. * * <p> * This dummy implementation is a trick so that Isis will render the property in a standalone table. Each of the * subclasses override with the "real" implementation. */ @Property( optionality = MANDATORY ) @PropertyLayout( hidden = Where.ALL_EXCEPT_STANDALONE_TABLES ) @MemberOrder(name="Detail",sequence = "7") public String getPostValue() { return null; } // ////////////////////////////////////// public static Comparator<DomainChangeJdoAbstract> compareByTimestampDescThenType(){ return ObjectContracts.compareBy("timestamp desc","type"); } public static Comparator<DomainChangeJdoAbstract> compareByTargetThenTimestampDescThenType(){ return ObjectContracts.compareBy("targetStr","timestamp desc","type"); } public static Comparator<DomainChangeJdoAbstract> compareByTargetThenUserThenTimestampDescThenType(){ return ObjectContracts.compareBy("targetStr","user","timestamp desc","type"); } public static Comparator<DomainChangeJdoAbstract> compareByUserThenTimestampDescThenType(){ return ObjectContracts.compareBy("user","timestamp desc","type"); } public static Comparator<DomainChangeJdoAbstract> compareByUserThenTargetThenTimestampDescThenType(){ return ObjectContracts.compareBy("user","targetStr","timestamp desc","type"); } // ////////////////////////////////////// // dependencies // ////////////////////////////////////// @javax.inject.Inject protected BookmarkService2 bookmarkService; @javax.inject.Inject protected MessageService messageService; @javax.inject.Inject protected MetaModelService3 metaModelService; }