001/**
002 *  Licensed to the Apache Software Foundation (ASF) under one or more
003 *  contributor license agreements.  See the NOTICE file distributed with
004 *  this work for additional information regarding copyright ownership.
005 *  The ASF licenses this file to You under the Apache License, Version 2.0
006 *  (the "License"); you may not use this file except in compliance with
007 *  the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 *  Unless required by applicable law or agreed to in writing, software
012 *  distributed under the License is distributed on an "AS IS" BASIS,
013 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 *  See the License for the specific language governing permissions and
015 *  limitations under the License.
016 */
017package org.apache.isis.objectstore.jdo.applib.service.command;
018
019import java.math.BigDecimal;
020import java.sql.Timestamp;
021import java.util.Map;
022import java.util.UUID;
023import java.util.concurrent.atomic.AtomicInteger;
024
025import javax.jdo.annotations.IdentityType;
026import javax.jdo.annotations.NotPersistent;
027
028import com.google.common.collect.Maps;
029
030import org.slf4j.Logger;
031import org.slf4j.LoggerFactory;
032
033import org.apache.isis.applib.DomainObjectContainer;
034import org.apache.isis.applib.annotation.ActionSemantics;
035import org.apache.isis.applib.annotation.ActionSemantics.Of;
036import org.apache.isis.applib.annotation.Bulk;
037import org.apache.isis.applib.annotation.Command.ExecuteIn;
038import org.apache.isis.applib.annotation.Command.Persistence;
039import org.apache.isis.applib.annotation.Hidden;
040import org.apache.isis.applib.annotation.Immutable;
041import org.apache.isis.applib.annotation.Mandatory;
042import org.apache.isis.applib.annotation.MemberGroupLayout;
043import org.apache.isis.applib.annotation.MemberOrder;
044import org.apache.isis.applib.annotation.MultiLine;
045import org.apache.isis.applib.annotation.Named;
046import org.apache.isis.applib.annotation.ObjectType;
047import org.apache.isis.applib.annotation.Programmatic;
048import org.apache.isis.applib.annotation.TypicalLength;
049import org.apache.isis.applib.annotation.Where;
050import org.apache.isis.applib.services.bookmark.Bookmark;
051import org.apache.isis.applib.services.bookmark.BookmarkService;
052import org.apache.isis.applib.services.command.Command;
053import org.apache.isis.applib.util.ObjectContracts;
054import org.apache.isis.applib.util.TitleBuffer;
055import org.apache.isis.objectstore.jdo.applib.service.DomainChangeJdoAbstract;
056import org.apache.isis.objectstore.jdo.applib.service.JdoColumnLength;
057import org.apache.isis.objectstore.jdo.applib.service.Util;
058import org.apache.isis.objectstore.jdo.applib.service.DomainChangeJdoAbstract.ChangeType;
059
060
061@javax.jdo.annotations.PersistenceCapable(
062        identityType=IdentityType.APPLICATION, 
063        table="IsisCommand")
064@javax.jdo.annotations.Queries( {
065    @javax.jdo.annotations.Query(
066            name="findByTransactionId", language="JDOQL",  
067            value="SELECT "
068                    + "FROM org.apache.isis.objectstore.jdo.applib.service.command.CommandJdo "
069                    + "WHERE transactionId == :transactionId "),
070    @javax.jdo.annotations.Query(
071            name="findBackgroundCommandByTransactionId", language="JDOQL",  
072            value="SELECT "
073                    + "FROM org.apache.isis.objectstore.jdo.applib.service.command.CommandJdo "
074                    + "WHERE transactionId == :transactionId "
075                    + "&& executeIn == 'BACKGROUND'"),
076    @javax.jdo.annotations.Query(
077            name="findBackgroundCommandsByParent", language="JDOQL",  
078            value="SELECT "
079                    + "FROM org.apache.isis.objectstore.jdo.applib.service.command.CommandJdo "
080                    + "WHERE parent == :parent "
081                    + "&& executeIn == 'BACKGROUND'"),
082    @javax.jdo.annotations.Query(
083            name="findBackgroundCommandsNotYetStarted", language="JDOQL",  
084            value="SELECT "
085                    + "FROM org.apache.isis.objectstore.jdo.applib.service.command.CommandJdo "
086                    + "WHERE executeIn == 'BACKGROUND' "
087                    + "&& startedAt == null "
088                    + "ORDER BY timestamp ASC "
089                    ),
090    @javax.jdo.annotations.Query(
091            name="findCurrent", language="JDOQL",  
092            value="SELECT "
093                    + "FROM org.apache.isis.objectstore.jdo.applib.service.command.CommandJdo "
094                    + "WHERE completedAt == null "
095                    + "ORDER BY timestamp DESC"),
096    @javax.jdo.annotations.Query(
097            name="findCompleted", language="JDOQL",  
098            value="SELECT "
099                    + "FROM org.apache.isis.objectstore.jdo.applib.service.command.CommandJdo "
100                    + "WHERE completedAt != null "
101                    + "&& executeIn == 'FOREGROUND' "
102                    + "ORDER BY timestamp DESC"),
103    @javax.jdo.annotations.Query(
104            name="findByTargetAndTimestampBetween", language="JDOQL",  
105            value="SELECT "
106                    + "FROM org.apache.isis.objectstore.jdo.applib.service.audit.AuditEntryJdo "
107                    + "WHERE targetStr == :targetStr " 
108                    + "&& timestamp >= :from " 
109                    + "&& timestamp <= :to "
110                    + "ORDER BY timestamp DESC"),
111    @javax.jdo.annotations.Query(
112            name="findByTargetAndTimestampAfter", language="JDOQL",  
113            value="SELECT "
114                    + "FROM org.apache.isis.objectstore.jdo.applib.service.audit.AuditEntryJdo "
115                    + "WHERE targetStr == :targetStr " 
116                    + "&& timestamp >= :from "
117                    + "ORDER BY timestamp DESC"),
118    @javax.jdo.annotations.Query(
119            name="findByTargetAndTimestampBefore", language="JDOQL",  
120            value="SELECT "
121                    + "FROM org.apache.isis.objectstore.jdo.applib.service.audit.AuditEntryJdo "
122                    + "WHERE targetStr == :targetStr " 
123                    + "&& timestamp <= :to "
124                    + "ORDER BY timestamp DESC"),
125    @javax.jdo.annotations.Query(
126            name="findByTarget", language="JDOQL",  
127            value="SELECT "
128                    + "FROM org.apache.isis.objectstore.jdo.applib.service.audit.AuditEntryJdo "
129                    + "WHERE targetStr == :targetStr " 
130                    + "ORDER BY timestamp DESC"),
131    @javax.jdo.annotations.Query(
132            name="findByTimestampBetween", language="JDOQL",  
133            value="SELECT "
134                    + "FROM org.apache.isis.objectstore.jdo.applib.service.audit.AuditEntryJdo "
135                    + "WHERE timestamp >= :from " 
136                    + "&&    timestamp <= :to "
137                    + "ORDER BY timestamp DESC"),
138    @javax.jdo.annotations.Query(
139            name="findByTimestampAfter", language="JDOQL",  
140            value="SELECT "
141                    + "FROM org.apache.isis.objectstore.jdo.applib.service.audit.AuditEntryJdo "
142                    + "WHERE timestamp >= :from "
143                    + "ORDER BY timestamp DESC"),
144    @javax.jdo.annotations.Query(
145            name="findByTimestampBefore", language="JDOQL",  
146            value="SELECT "
147                    + "FROM org.apache.isis.objectstore.jdo.applib.service.audit.AuditEntryJdo "
148                    + "WHERE timestamp <= :to "
149                    + "ORDER BY timestamp DESC"),
150    @javax.jdo.annotations.Query(
151            name="find", language="JDOQL",  
152            value="SELECT "
153                    + "FROM org.apache.isis.objectstore.jdo.applib.service.audit.AuditEntryJdo "
154                    + "ORDER BY timestamp DESC")
155})
156@ObjectType("IsisCommand")
157@MemberGroupLayout(
158        columnSpans={6,0,6,12}, 
159        left={"Identifiers","Target","Notes"},
160        right={"Detail","Timings","Results"})
161@Named("Command")
162@Immutable
163public class CommandJdo extends DomainChangeJdoAbstract implements Command {
164
165    @SuppressWarnings("unused")
166    private static final Logger LOG = LoggerFactory.getLogger(CommandJdo.class);
167
168    public CommandJdo() {
169        super(ChangeType.COMMAND);
170    }
171
172
173
174    // //////////////////////////////////////
175    // Identification
176    // //////////////////////////////////////
177
178    public String title() {
179        final TitleBuffer buf = new TitleBuffer();
180        buf.append(getTargetStr());
181        buf.append(" ").append(getMemberIdentifier());
182        return buf.toString();
183    }
184
185
186
187    // //////////////////////////////////////
188    // user (property)
189    // //////////////////////////////////////
190
191    private String user;
192
193    @javax.jdo.annotations.Column(allowsNull="false", length=JdoColumnLength.USER_NAME)
194    @MemberOrder(name="Identifiers", sequence = "10")
195    public String getUser() {
196        return user;
197    }
198
199    public void setUser(final String user) {
200        this.user = user;
201    }
202
203
204
205    // //////////////////////////////////////
206    // timestamp (property)
207    // //////////////////////////////////////
208
209    private Timestamp timestamp;
210
211    /**
212     * The date/time at which this action was created.
213     */
214    @javax.jdo.annotations.Persistent
215    @javax.jdo.annotations.Column(allowsNull="false")
216    @MemberOrder(name="Identifiers", sequence = "20")
217    public Timestamp getTimestamp() {
218        return timestamp;
219    }
220    
221    /**
222     * <b>NOT API</b>: intended to be called only by the framework.
223     */
224    public void setTimestamp(final Timestamp timestamp) {
225        this.timestamp = timestamp;
226    }
227
228    
229    // //////////////////////////////////////
230    // executor (property)
231    // //////////////////////////////////////
232    
233    private Executor executor;
234    
235    @Programmatic
236    @javax.jdo.annotations.NotPersistent
237    @Override
238    public Executor getExecutor() {
239        return executor;
240    }
241
242    @Override
243    public void setExecutor(Executor nature) {
244        this.executor = nature;
245    }
246
247    // //////////////////////////////////////
248    // executeIn (property)
249    // //////////////////////////////////////
250
251    private ExecuteIn executeIn;
252
253    /**
254     * Whether the action was invoked explicitly by the user, or scheduled as a background
255     * task, or as for some other reason, eg a side-effect of rendering an object due to 
256     * get-after-post).
257     */
258    @javax.jdo.annotations.Column(allowsNull="false", length=JdoColumnLength.Command.EXECUTE_IN)
259    @MemberOrder(name="Identifiers", sequence = "32")
260    @Override
261    public ExecuteIn getExecuteIn() {
262        return executeIn;
263    }
264    
265    /**
266     * <b>NOT API</b>: intended to be called only by the framework.
267     */
268    @Override
269    public void setExecuteIn(ExecuteIn nature) {
270        this.executeIn = nature;
271    }
272
273
274    // //////////////////////////////////////
275    // parent (property)
276    // //////////////////////////////////////
277
278    private Command parent;
279    
280    @Override
281    @javax.jdo.annotations.Persistent
282    @javax.jdo.annotations.Column(name="parentTransactionId", allowsNull="true")
283    @Hidden(where=Where.PARENTED_TABLES)
284    @MemberOrder(name="Identifiers",sequence = "40")
285    public Command getParent() {
286        return parent;
287    }
288
289    @Override
290    public void setParent(Command parent) {
291        this.parent = parent;
292    }
293
294    
295    // //////////////////////////////////////
296    // transactionId (property)
297    // //////////////////////////////////////
298
299        
300    private UUID transactionId;
301
302    /**
303     * The unique identifier (a GUID) of the transaction in which this command occurred.
304     */
305    @javax.jdo.annotations.PrimaryKey
306    @javax.jdo.annotations.Column(allowsNull="false", length=JdoColumnLength.TRANSACTION_ID)
307    @TypicalLength(JdoColumnLength.TRANSACTION_ID)
308    @MemberOrder(name="Identifiers",sequence = "50")
309    @Override
310    public UUID getTransactionId() {
311        return transactionId;
312    }
313
314    /**
315     * <b>NOT API</b>: intended to be called only by the framework.
316     * 
317     * <p>
318     * Implementation notes: copied over from the Isis transaction when the command is persisted.
319     */
320    @Override
321    public void setTransactionId(final UUID transactionId) {
322        this.transactionId = transactionId;
323    }
324
325    
326    // //////////////////////////////////////
327    // targetClass (property)
328    // //////////////////////////////////////
329
330    private String targetClass;
331
332    @javax.jdo.annotations.Column(allowsNull="false", length=JdoColumnLength.TARGET_CLASS)
333    @TypicalLength(30)
334    @MemberOrder(name="Target", sequence = "10")
335    @Named("Class")
336    public String getTargetClass() {
337        return targetClass;
338    }
339
340    public void setTargetClass(final String targetClass) {
341        this.targetClass = Util.abbreviated(targetClass, JdoColumnLength.TARGET_CLASS);
342    }
343
344
345    // //////////////////////////////////////
346    // targetAction (property)
347    // //////////////////////////////////////
348    
349    private String targetAction;
350    
351    @javax.jdo.annotations.Column(allowsNull="false", length=JdoColumnLength.TARGET_ACTION)
352    @Mandatory
353    @Hidden(where=Where.NOWHERE)
354    @TypicalLength(30)
355    @MemberOrder(name="Target", sequence = "20")
356    @Named("Action")
357    public String getTargetAction() {
358        return targetAction;
359    }
360    
361    public void setTargetAction(final String targetAction) {
362        this.targetAction = Util.abbreviated(targetAction, JdoColumnLength.TARGET_ACTION);
363    }
364    
365
366    // //////////////////////////////////////
367    // target (property)
368    // openTargetObject (action)
369    // //////////////////////////////////////
370
371    private String targetStr;
372    @javax.jdo.annotations.Column(allowsNull="false", length=JdoColumnLength.BOOKMARK, name="target")
373    @Hidden(where=Where.ALL_TABLES)
374    @MemberOrder(name="Target", sequence="30")
375    @Named("Object")
376    public String getTargetStr() {
377        return targetStr;
378    }
379
380    public void setTargetStr(final String targetStr) {
381        this.targetStr = targetStr;
382    }
383
384    // //////////////////////////////////////
385    // arguments (property)
386    // //////////////////////////////////////
387    
388    private String arguments;
389    
390    @javax.jdo.annotations.Column(allowsNull="true", jdbcType="CLOB")
391    @MultiLine(numberOfLines=7)
392    @Hidden(where=Where.ALL_TABLES)
393    @MemberOrder(name="Target",sequence = "40")
394    public String getArguments() {
395        return arguments;
396    }
397    
398    public void setArguments(final String arguments) {
399        this.arguments = arguments;
400    }
401
402    
403
404    // //////////////////////////////////////
405    // memberIdentifier (property)
406    // //////////////////////////////////////
407
408    private String memberIdentifier;
409    
410    @javax.jdo.annotations.Column(allowsNull="false", length=JdoColumnLength.MEMBER_IDENTIFIER)
411    @TypicalLength(60)
412    @Hidden(where=Where.ALL_TABLES)
413    @MemberOrder(name="Detail",sequence = "1")
414    public String getMemberIdentifier() {
415        return memberIdentifier;
416    }
417
418    public void setMemberIdentifier(final String memberIdentifier) {
419        this.memberIdentifier = Util.abbreviated(memberIdentifier, JdoColumnLength.MEMBER_IDENTIFIER);
420    }
421
422
423
424    // //////////////////////////////////////
425    // memento (property)
426    // //////////////////////////////////////
427    
428    private String memento;
429    
430    @javax.jdo.annotations.Column(allowsNull="true", jdbcType="CLOB")
431    @MultiLine(numberOfLines=9)
432    @Hidden(where=Where.ALL_TABLES)
433    @MemberOrder(name="Detail",sequence = "30")
434    public String getMemento() {
435        return memento;
436    }
437    
438    public void setMemento(final String memento) {
439        this.memento = memento;
440    }
441
442
443
444    // //////////////////////////////////////
445    // startedAt (derived property)
446    // //////////////////////////////////////
447    
448    private Timestamp startedAt;
449
450    @javax.jdo.annotations.Persistent
451    @javax.jdo.annotations.Column(allowsNull="true")
452    @MemberOrder(name="Timings", sequence = "3")
453    public Timestamp getStartedAt() {
454        return startedAt;
455    }
456
457    /**
458     * <b>NOT API</b>: intended to be called only by the framework.
459     */
460    public void setStartedAt(final Timestamp startedAt) {
461        this.startedAt = startedAt;
462    }
463    
464    
465    
466    // //////////////////////////////////////
467    // completedAt (property)
468    // //////////////////////////////////////
469
470    private Timestamp completedAt;
471
472    /**
473     * The date/time at which this interaction completed.
474     */
475    @javax.jdo.annotations.Persistent
476    @javax.jdo.annotations.Column(allowsNull="true")
477    @MemberOrder(name="Timings", sequence = "4")
478    @Override
479    public Timestamp getCompletedAt() {
480        return completedAt;
481    }
482
483    @Override
484    public void setCompletedAt(final Timestamp completed) {
485        this.completedAt = completed;
486    }
487
488
489    // //////////////////////////////////////
490    // duration (derived property)
491    // //////////////////////////////////////
492
493    /**
494     * The number of seconds (to 3 decimal places) that this interaction lasted.
495     * 
496     * <p>
497     * Populated only if it has {@link #getCompletedAt() completed}.
498     */
499    @javax.validation.constraints.Digits(integer=5, fraction=3)
500    @Named("Duration")
501    @MemberOrder(name="Timings", sequence = "7")
502    public BigDecimal getDuration() {
503        return Util.durationBetween(getStartedAt(), getCompletedAt());
504    }
505
506
507
508    // //////////////////////////////////////
509    // complete (derived property)
510    // //////////////////////////////////////
511    
512
513    @javax.jdo.annotations.NotPersistent
514    @MemberOrder(name="Timings", sequence = "8")
515    @Hidden(where=Where.OBJECT_FORMS)
516    public boolean isComplete() {
517        return getCompletedAt() != null;
518    }
519
520    
521    
522    // //////////////////////////////////////
523    // state (derived property)
524    // //////////////////////////////////////
525
526    @javax.jdo.annotations.NotPersistent
527    @MemberOrder(name="Results",sequence = "10")
528    @Hidden(where=Where.OBJECT_FORMS)
529    @Named("Result")
530    public String getResultSummary() {
531        if(getCompletedAt() == null) {
532            return "";
533        }
534        if(getException() != null) {
535            return "EXCEPTION";
536        } 
537        if(getResultStr() != null) {
538            return "OK";
539        } else {
540            return "OK (VOID)";
541        }
542    }
543
544    
545    // //////////////////////////////////////
546    // result (property)
547    // openResultObject (action)
548    // //////////////////////////////////////
549    
550    @Programmatic
551    @Override
552    public Bookmark getResult() {
553        return Util.bookmarkFor(getResultStr());
554    }
555
556    @Programmatic
557    @Override
558    public void setResult(Bookmark result) {
559        setResultStr(Util.asString(result));
560    }
561
562    // //////////////////////////////////////
563    
564    private String resultStr;
565
566    @javax.jdo.annotations.Column(allowsNull="true", length=JdoColumnLength.BOOKMARK, name="result")
567    @Hidden(where=Where.ALL_TABLES)
568    @Named("Result Bookmark")
569    @MemberOrder(name="Results", sequence="25")
570    public String getResultStr() {
571        return resultStr;
572    }
573
574    public void setResultStr(final String resultStr) {
575        this.resultStr = resultStr;
576    }
577
578    // //////////////////////////////////////
579
580    @ActionSemantics(Of.SAFE)
581    @MemberOrder(name="ResultStr", sequence="1")
582    @Named("Open")
583    public Object openResultObject() {
584        return Util.lookupBookmark(getResult(), bookmarkService, container);
585    }
586    public boolean hideOpenResultObject() {
587        return getResult() == null;
588    }
589
590
591    // //////////////////////////////////////
592    // exception (property)
593    // causedException (derived property)
594    // showException (associated action)
595    // //////////////////////////////////////
596
597    private String exception;
598
599    /**
600     * Stack trace of any exception that might have occurred if this interaction/transaction aborted.
601     * 
602     * <p>
603     * Not visible in the UI, but accessible 
604     * <p>
605     * Not part of the applib API, because the default implementation is not persistent
606     * and so there's no object that can be accessed to be annotated.
607     */
608    @javax.jdo.annotations.Column(allowsNull="true", jdbcType="CLOB")
609    @Hidden
610    @Override
611    public String getException() {
612        return exception;
613    }
614
615    @Override
616    public void setException(final String exception) {
617        this.exception = exception;
618    }
619    
620    
621    // //////////////////////////////////////
622    
623    @javax.jdo.annotations.NotPersistent
624    @MemberOrder(name="Results",sequence = "30")
625    @Hidden(where=Where.ALL_TABLES)
626    public boolean isCausedException() {
627        return getException() != null;
628    }
629
630    
631    // //////////////////////////////////////
632    
633    @ActionSemantics(Of.SAFE)
634    @MemberOrder(name="causedException", sequence = "1")
635    public String showException() {
636        return getException();
637    }
638    public boolean hideShowException() {
639        return !isCausedException();
640    }
641
642
643    // //////////////////////////////////////
644    // next(...) impl
645    // //////////////////////////////////////
646
647    private final Map<String, AtomicInteger> sequenceByName = Maps.newHashMap();
648
649
650
651    @Programmatic
652    @Override
653    public int next(String sequenceName) {
654        AtomicInteger next = sequenceByName.get(sequenceName);
655        if(next == null) {
656            next = new AtomicInteger(0);
657            sequenceByName.put(sequenceName, next);
658        } else {
659            next.incrementAndGet();
660        }
661        return next.get();
662    }
663
664    
665    // //////////////////////////////////////
666    // persistence (programmatic)
667    // //////////////////////////////////////
668
669    private Persistence persistence;
670    
671    @javax.jdo.annotations.NotPersistent
672    @Programmatic
673    @Override
674    public Persistence getPersistence() {
675        return persistence;
676    }
677
678    @Override
679    public void setPersistence(Persistence persistence) {
680        this.persistence = persistence;
681    }
682
683
684    // //////////////////////////////////////
685    // setPersistHint (SPI impl)
686    // //////////////////////////////////////
687    
688    private boolean persistHint;
689
690    @NotPersistent
691    @Programmatic
692    public boolean isPersistHint() {
693        return persistHint;
694    }
695    
696    @Programmatic
697    @Override
698    public void setPersistHint(boolean persistHint) {
699        this.persistHint = persistHint;
700    }
701
702    
703    // //////////////////////////////////////
704    
705    @Programmatic
706    boolean shouldPersist() {
707        if(Persistence.PERSISTED == getPersistence()) {
708            return true;
709        }
710        if(Persistence.IF_HINTED == getPersistence()) {
711            return isPersistHint();
712        }
713        return false;
714    }
715
716
717
718    // //////////////////////////////////////
719    // toString
720    // //////////////////////////////////////
721
722    @Override
723    public String toString() {
724        return ObjectContracts.toString(this, "targetStr,memberIdentifier,user,startedAt,completedAt,duration,transactionId");
725    }
726
727    
728    
729    // //////////////////////////////////////
730    // dependencies
731    // //////////////////////////////////////
732    
733
734    @javax.inject.Inject
735    private BookmarkService bookmarkService;
736    
737    @javax.inject.Inject
738    private DomainObjectContainer container;
739
740}