View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.myfaces.orchestra.conversation.versioning;
21  
22  import java.util.Map;
23  import java.util.Stack;
24  
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  import org.apache.myfaces.orchestra.conversation.Conversation;
28  import org.apache.myfaces.orchestra.conversation.ConversationContext;
29  import org.apache.myfaces.orchestra.conversation.ConversationDataHolder;
30  import org.apache.myfaces.orchestra.conversation.ConversationFactory;
31  import org.apache.myfaces.orchestra.conversation.SerializingConversationDataHolder;
32  import org.apache.myfaces.orchestra.conversation.versioning.spring.SpringVersioningScope;
33  
34  /**
35   * A special kind of conversation which provides snapshot- and rollback functionality.
36   * Both commands can be triggered from within a version scoped managed-bean.
37   * <p/>
38   * A typical usecase may be a multi-edit page whith different levels of edit-/cancel actions
39   * on a hierarchical tree of objects. (e.g. Company -> persons -> addresses)
40   * <p/>
41   * Triggering a snapshot will "copy" the whole conversational data, means all the beans which
42   * are currently located in this conversation.
43   * The "copy" process currently only supports serializing {@link SerializingConversationDataHolder},
44   * cloning maybe added in future versions. Triggering snapshots should be done with care, since
45   * it adds some overhead to memory and is therefore no that well scaleable.
46   */
47  public class VersioningConversation extends Conversation
48  {
49      private final Log log = LogFactory.getLog(VersioningConversation.class);
50  
51      // The placeholders for user references to a snapshot
52      private Stack<SavePoint> savePoints = new Stack<SavePoint>();
53  
54      // The real saved conversational data - beans
55      private Stack<ConversationDataHolder> conversationVersions = new Stack<ConversationDataHolder>();
56  
57      // choosen strategy to store the data
58      private String versioningStrategy;
59  
60      public VersioningConversation(ConversationContext conversationContext,
61              String name, ConversationFactory factory, String strategy)
62      {
63          super(conversationContext, name, factory);
64          versioningStrategy = strategy;
65      }
66  
67      /**
68       * Creates a snapshot of the actual conversational data. Should be held
69       * inside the managed-bean for later rollback reference.
70       * <p/>
71       * Does NOT add any overhead directly to the managed-bean. It represents
72       * just a placeholder.
73       *
74       * @return SavePoint a placeholder for a current snaphsot
75       */
76      public SavePoint createSavePoint()
77      {
78          SavePoint savePoint = new SavePoint();
79          createConversationDataSnapshot(savePoint);
80          return savePoint;
81      }
82  
83      /**
84       * Creates a snapshot of the actual conversational data.
85       * <p/>
86       *
87       * @param savePointName The name of the snapshot; Should be "remembered"
88       *                      for later rollback references.
89       */
90      public void createSavePoint(String savePointName)
91      {
92          SavePoint savePoint = new SavePoint(savePointName);
93          createConversationDataSnapshot(savePoint);
94      }
95  
96      /**
97       * Reverts to the given snapshot (SavePoint).
98       * Seeks the snapshot, gets the corresponding data, restores it and frees it out of the memory.
99       */
100     public void revertToSavePoint(SavePoint savePoint)
101     {
102         int savePointIndex = savePoints.indexOf(savePoint);
103         removeConversationDataSnapshot(savePointIndex);
104     }
105 
106     /**
107      * Reverts to a snapshot via the given SavePoint name.
108      */
109     public void revertToSavePoint(String savePointName)
110     {
111         int savePointIndex = findSavePointIndex(savePointName);
112         removeConversationDataSnapshot(savePointIndex);
113     }
114 
115     /**
116      * Reverts to the last snapshot.
117      */
118     public void revertToLastSavePoint()
119     {
120         if(!savePoints.isEmpty())
121         {
122             SavePoint lastSavePoint = savePoints.pop();
123             ConversationDataHolder conversationDataHolder = conversationVersions.pop();
124             Map restoredBeans = conversationDataHolder.getConversationBeans();
125             setBeans(restoredBeans);
126         }
127         else
128         {
129             log.error("No savepoints stored before. Unable to revert to last save point!");
130         }
131     }
132 
133     /**
134      * Frees all SavePoints and versioned conversational data
135      */
136     public void clearAllSavePoints()
137     {
138         savePoints.removeAllElements();
139         conversationVersions.removeAllElements();
140     }
141 
142     private void createConversationDataSnapshot(SavePoint savePoint)
143     {
144         ConversationDataHolder conversationDataHolder = createConversationDataHolder();
145         conversationVersions.push(conversationDataHolder);
146         savePoints.push(savePoint);
147     }
148 
149     /**
150      * Restores a previous snapshot and sets it as the actual versioned conversational data.
151      * Frees the actual SavePoint and the corresponding data at the given index.
152      */
153     private void removeConversationDataSnapshot(int savePointIndex)
154     {
155         if(savePointIndex != -1)
156         {
157             savePoints.remove(savePointIndex);
158             ConversationDataHolder conversationDataHolder = conversationVersions.get(savePointIndex);
159             Map restoredBeans = conversationDataHolder.getConversationBeans();
160             setBeans(restoredBeans);
161             conversationVersions.remove(savePointIndex);
162         }
163         else
164         {
165             log.error("No savepoint to revert found!");
166         }
167     }
168 
169     /**
170      * Chooses the DataHolder regarding the strategy and stores the conversational data "somewhere"
171      */
172     private ConversationDataHolder createConversationDataHolder()
173     {
174         if(versioningStrategy.equals(SpringVersioningScope.VERSIONING_STRATEGY_SERIALIZATION))
175         {
176             return new SerializingConversationDataHolder(getBeans());
177         }
178         else
179         {
180             throw new UnsupportedOperationException("Cloning not implemented yet!");
181         }
182     }
183 
184     private int findSavePointIndex(String savePointName)
185     {
186         for(int i = 0; i < savePoints.size(); i++)
187         {
188             SavePoint savePoint = savePoints.elementAt(i);
189             String name = savePoint.getSavePointName();
190             if(name.equals(savePointName))
191             {
192                 return i;
193             }
194         }
195         return -1;
196     }
197 
198 }