View Javadoc

1   /**
2    * This file Copyright (c) 2003-2011 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.module.admininterface.commands;
35  
36  import info.magnolia.cms.core.Content;
37  import info.magnolia.cms.exchange.ExchangeException;
38  import info.magnolia.cms.i18n.MessagesManager;
39  import info.magnolia.cms.util.AlertUtil;
40  import info.magnolia.context.Context;
41  
42  import java.util.ArrayList;
43  import java.util.Collection;
44  import java.util.Collections;
45  import java.util.Comparator;
46  import java.util.List;
47  import java.util.Map;
48  
49  import javax.jcr.RepositoryException;
50  import javax.jcr.Node;
51  import javax.jcr.NodeIterator;
52  
53  import org.apache.commons.lang.StringUtils;
54  import org.slf4j.Logger;
55  import org.slf4j.LoggerFactory;
56  
57  
58  /**
59   * the activation command which do real activation
60   * @author jackie
61   * $Id: ActivationCommand.java 41137 2011-01-06 18:19:25Z gjoseph $
62   */
63  public class ActivationCommand extends BaseActivationCommand {
64  
65      /**
66       * Log
67       */
68      private static Logger log = LoggerFactory.getLogger(ActivationCommand.class);
69  
70      private boolean recursive;
71  
72      private String versionNumber;
73  
74      private List versionMap;
75  
76      /**
77       * Execute activation
78       */
79      public boolean execute(Context ctx) {
80          boolean success = false;
81          try {
82              log.debug("Will activate content from {} repository with uuid {} and path {}", new Object[] {getRepository(), getUuid(), getPath()});
83              final Content originalState = getNode(ctx);
84              Content thisState = originalState;
85              if (!recursive) {
86                  if (StringUtils.isNotEmpty(getVersion())) {
87                      try {
88                          thisState = thisState.getVersionedContent(getVersion());
89                      } catch (RepositoryException re) {
90                          log.error("Failed to get version "+getVersion()+" for "+thisState.getHandle(), re);
91                      }
92                  }
93                  success = activateUpdate(ctx, originalState);
94              } else {
95                  success = activateBulkUpdate(ctx, getNode(ctx));
96              }
97          }
98          catch (Exception e) {
99              log.error("can't activate", e);
100             AlertUtil.setException(MessagesManager.get("tree.error.activate"), e, ctx);
101         }
102         return success;
103     }
104 
105     private boolean activateUpdate(Context ctx, Content thisState) throws ExchangeException, RepositoryException {
106         boolean success;
107         String parentPath = StringUtils.substringBeforeLast(thisState.getHandle(), "/");
108         if (StringUtils.isEmpty(parentPath)) {
109             parentPath = "/";
110         }
111         log.debug("Activate content {} as a child of {}", new Object[] {thisState.getName(), parentPath});
112         // make multiple activations instead of a big bulk
113         List orderInfo = getOrderingInfo(thisState);
114         if (StringUtils.isNotEmpty(getVersion())) {
115             try {
116                 thisState = thisState.getVersionedContent(getVersion());
117             } catch (RepositoryException re) {
118                 // TODO: is this correct? should not we fail completely rather then silently ignore versions?
119                 log.error("Failed to get version "+getVersion()+" for "+thisState.getHandle() + ". Activating current content instead.", re);
120             }
121         }
122         getSyndicator().activate(parentPath, thisState, orderInfo);
123         log.debug("exec successfully.");
124         success = true;
125         return success;
126     }
127 
128     private boolean activateBulkUpdate(Context ctx, Content thisState) throws ExchangeException, RepositoryException {
129         boolean success;
130         // make multiple activations instead of a big bulk
131         List versionMap = getVersionMap();
132         if (versionMap == null) {
133             String parentPath = StringUtils.substringBeforeLast(thisState.getHandle(), "/");
134             if (StringUtils.isEmpty(parentPath)) {
135                 parentPath = "/";
136             }
137             log.debug("Activate content {} as a child of {}", new Object[] {thisState.getName(), parentPath});
138             activateRecursive(parentPath, thisState, ctx);
139         } else {
140             activateRecursive(ctx, versionMap);
141         }
142         log.debug("exec successfully.");
143         success = true;
144         return success;
145     }
146 
147     /**
148      * Activate recursively. This is done one by one to send only small peaces (memory friendly).
149      * @param parentPath
150      * @param node
151      * @throws ExchangeException
152      * @throws RepositoryException
153      */
154     protected void activateRecursive(String parentPath, Content node, Context ctx)
155             throws ExchangeException, RepositoryException {
156 
157         getSyndicator().activate(parentPath, node, getOrderingInfo(node));
158 
159         Collection children = node.getChildren(new Content.ContentFilter() {
160             public boolean accept(Content content) {
161                 try {
162                     return !getRule().isAllowed(content.getNodeTypeName());
163                 }
164                 catch (RepositoryException e) {
165                     log.error("can't get nodetype", e);
166                     return false;
167                 }
168             }
169         });
170 
171         // FYI: need to reverse order of child activation since content ordering info is also bottom-up
172         // Hackish at best. If changing this, don't forget to also change other activateRecursive() method
173         // and most importantly ensure that ordering of siblings in ReceiveFilter is done in same direction!
174         Content[] childArray = (Content[]) children.toArray(new Content[children.size()]);
175         for (int i = childArray.length - 1; i >=0; i--) {
176             this.activateRecursive(node.getHandle(), childArray[i], ctx);
177         }
178     }
179 
180     /**
181      * @param ctx
182      * @param versionMap
183      * */
184     protected void activateRecursive(Context ctx, List versionMap)
185             throws ExchangeException, RepositoryException {
186         // activate all uuid's present in versionMap
187         Map<String, Object>[] versions = (Map<String, Object>[]) versionMap.toArray(new Map[0]);
188         // add path and order info into the entries
189         for (int i = 0; i < versions.length; i++) {
190             Map<String, Object> entry = versions[i];
191             String uuid = (String) entry.get("uuid");
192             if (StringUtils.equalsIgnoreCase("class", uuid)) {
193                 // TODO: this should not happen in between the serialized list, somewhere a bug
194                 // for the moment simply ignore it
195                 versionMap.remove(entry);
196             }
197             try {
198                 Content content = ctx.getHierarchyManager(getRepository()).getContentByUUID(uuid);
199                 entry.put("handle", content.getHandle());
200                 entry.put("index", i);
201             } catch (RepositoryException re) {
202                 log.error("Failed to activate node with UUID : "+uuid);
203                 log.error(re.getMessage());
204                 versionMap.remove(entry);
205             }
206         }
207         versions = null;
208 
209         // versionMap is a flat list of all activated content. We need to ensure that the content is ordered from top down and siblings are activated from bottom up
210         Collections.sort((List<Map<String, Object>>) versionMap, new Comparator<Map<String, Object>>() {
211 
212             public int compare(Map<String, Object> o1, Map<String, Object> o2) {
213                 String handle1 = (String) o1.get("handle");
214                 String handle2 = (String) o2.get("handle");
215                 if (handle2.startsWith(handle1)) {
216                     // o2 is child of o1, say o1 is smaller to get it ordered BEFORE o2
217                     return -1;
218                 }
219                 String parent1 = StringUtils.substringBeforeLast(handle1, "/");
220                 String parent2 = StringUtils.substringBeforeLast(handle2, "/");
221                 if (parent1.equals(parent2)) {
222                     // siblings ... to reverse order, the higher index value get ordered before lower values index
223                     int idx1 = (Integer) o1.get("index");
224                     int idx2 = (Integer) o2.get("index");
225                     // index is generated in the loop above and can be never same for 2 items ... skip equality case
226                     return idx1 < idx2 ? 1 : -1;
227                 }
228 
229                 // unrelated entries, the one closer to the root should be returned first
230                 int dirLevels1 = StringUtils.countMatches(handle1, "/");
231                 int dirLevels2 = StringUtils.countMatches(handle2, "/");
232                 // since parents are checked above, the equality case here means different hierarchy of same depth and is irrelevant to activation order
233                 return dirLevels1 < dirLevels2 ? -1 : 1;
234             }});
235 
236         // FYI: need to reverse order of child activation since content ordering info is also bottom-up
237         // Hackish at best. If changing this, don't forget to also change other activateRecursive() method
238         // and most importantly ensure that ordering of siblings in ReceiveFilter is done in same direction!
239         for (Map entry : (List<Map>) versionMap) {
240 
241             String uuid = (String) entry.get("uuid");
242             String versionNumber = (String) entry.get("version");
243             if (StringUtils.equalsIgnoreCase("class", uuid)) {
244                 // TODO: this should not happen in between the serialized list, somewhere a bug
245                 // for the moment simply ignore it
246                 continue;
247             }
248             try {
249                 Content content = ctx.getHierarchyManager(getRepository()).getContentByUUID(uuid);
250                 // NOTE : on activation of the version use current hierarchy to order
251                 // since versioning operation does not preserve order anywhere
252                 List orderedList = getOrderingInfo(content);
253                 String parentPath = content.getParent().getHandle();
254                 content = content.getVersionedContent(versionNumber);
255                 // add order info for the first node as it represents the parent in a tree
256                 getSyndicator().activate(parentPath, content, orderedList);
257             } catch (RepositoryException re) {
258                 log.error("Failed to activate node with UUID : "+uuid);
259                 log.error(re.getMessage());
260             }
261         }
262     }
263 
264     /**
265      * collect node UUID of the siblings in the exact order as it should be written on
266      * subscribers
267      * @param node
268      * */
269     protected List getOrderingInfo(Content node) {
270         //do not use magnolia Content class since these objects are only meant for a single use to read UUID
271         // TODO: what's wrong with using magnolia content???
272         List siblings = new ArrayList();
273         Node thisNode = node.getJCRNode();
274         try {
275             String thisNodeType = node.getNodeTypeName();
276             String thisNodeUUID = node.getUUID();
277             NodeIterator nodeIterator = thisNode.getParent().getNodes();
278             while (nodeIterator.hasNext()) { // only collect elements after this node
279                 Node sibling = nodeIterator.nextNode();
280                 // skip till the actual position
281                 if (sibling.isNodeType(thisNodeType)) {
282                     if (thisNodeUUID.equalsIgnoreCase(sibling.getUUID())) break;
283                 }
284             }
285             while (nodeIterator.hasNext()) {
286                 Node sibling = nodeIterator.nextNode();
287                 if (sibling.isNodeType(thisNodeType)) {
288                     siblings.add(sibling.getUUID());
289                 }
290             }
291         } catch (RepositoryException re) {
292             // do not throw this exception, if it fails simply do not add any ordering info
293             log.error("Failed to get Ordering info", re);
294         }
295         return siblings;
296     }
297 
298     /**
299      * @return the recursive
300      */
301     public boolean isRecursive() {
302         return recursive;
303     }
304 
305     /**
306      * @param recursive the recursive to set
307      */
308     public void setRecursive(boolean recursive) {
309         this.recursive = recursive;
310     }
311 
312     /**
313      * @param number version number to be set for activation
314      * */
315     public void setVersion(String number) {
316         this.versionNumber = number;
317     }
318 
319     /**
320      * @return version number
321      * */
322     public String getVersion() {
323         return this.versionNumber;
324     }
325 
326     /**
327      * @param versionMap version map to be set for activation
328      * */
329     public void setVersionMap(List versionMap) {
330         this.versionMap = versionMap;
331     }
332 
333     /**
334      * @return version map
335      * */
336     public List getVersionMap() {
337         return this.versionMap;
338     }
339 
340     @Override
341     public void release() {
342         super.release();
343         this.versionMap = null;
344         this.recursive = false;
345         this.versionNumber = null;
346     }
347 
348 }