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.Node;
50  import javax.jcr.NodeIterator;
51  import javax.jcr.RepositoryException;
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$
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      @Override
80      public boolean execute(Context ctx) {
81          boolean success = false;
82          try {
83              log.debug("Will activate content from {} repository with uuid {} and path {}", new Object[] {getRepository(), getUuid(), getPath()});
84              final Content originalState = getNode(ctx);
85              Content thisState = originalState;
86              if (!recursive) {
87                  if (StringUtils.isNotEmpty(getVersion())) {
88                      try {
89                          thisState = thisState.getVersionedContent(getVersion());
90                      } catch (RepositoryException re) {
91                          log.error("Failed to get version "+getVersion()+" for "+thisState.getHandle(), re);
92                      }
93                  }
94                  success = activateUpdate(ctx, originalState);
95              } else {
96                  success = activateBulkUpdate(ctx, getNode(ctx));
97              }
98          }
99          catch (Exception e) {
100             log.error("can't activate", e);
101             AlertUtil.setException(MessagesManager.get("tree.error.activate"), e, ctx);
102         }
103         return success;
104     }
105 
106     private boolean activateUpdate(Context ctx, Content thisState) throws ExchangeException, RepositoryException {
107         boolean success;
108         String parentPath = StringUtils.substringBeforeLast(thisState.getHandle(), "/");
109         if (StringUtils.isEmpty(parentPath)) {
110             parentPath = "/";
111         }
112         log.debug("Activate content {} as a child of {}", new Object[] {thisState.getName(), parentPath});
113         // make multiple activations instead of a big bulk
114         List orderInfo = getOrderingInfo(thisState);
115         if (StringUtils.isNotEmpty(getVersion())) {
116             try {
117                 thisState = thisState.getVersionedContent(getVersion());
118             } catch (RepositoryException re) {
119                 // TODO: is this correct? should not we fail completely rather then silently ignore versions?
120                 log.error("Failed to get version "+getVersion()+" for "+thisState.getHandle() + ". Activating current content instead.", re);
121             }
122         }
123         getSyndicator().activate(parentPath, thisState, orderInfo);
124         log.debug("exec successfully.");
125         success = true;
126         return success;
127     }
128 
129     private boolean activateBulkUpdate(Context ctx, Content thisState) throws ExchangeException, RepositoryException {
130         boolean success;
131         // make multiple activations instead of a big bulk
132         List versionMap = getVersionMap();
133         if (versionMap == null) {
134             String parentPath = StringUtils.substringBeforeLast(thisState.getHandle(), "/");
135             if (StringUtils.isEmpty(parentPath)) {
136                 parentPath = "/";
137             }
138             log.debug("Activate content {} as a child of {}", new Object[] {thisState.getName(), parentPath});
139             activateRecursive(parentPath, thisState, ctx);
140         } else {
141             activateRecursive(ctx, versionMap);
142         }
143         log.debug("exec successfully.");
144         success = true;
145         return success;
146     }
147 
148     /**
149      * Activate recursively. This is done one by one to send only small peaces (memory friendly).
150      * @param parentPath
151      * @param node
152      * @throws ExchangeException
153      * @throws RepositoryException
154      */
155     protected void activateRecursive(String parentPath, Content node, Context ctx)
156     throws ExchangeException, RepositoryException {
157 
158         getSyndicator().activate(parentPath, node, getOrderingInfo(node));
159 
160         Collection children = node.getChildren(new Content.ContentFilter() {
161             @Override
162             public boolean accept(Content content) {
163                 try {
164                     return !getRule().isAllowed(content.getNodeTypeName());
165                 }
166                 catch (RepositoryException e) {
167                     log.error("can't get nodetype", e);
168                     return false;
169                 }
170             }
171         });
172 
173         // FYI: need to reverse order of child activation since content ordering info is also bottom-up
174         // Hackish at best. If changing this, don't forget to also change other activateRecursive() method
175         // and most importantly ensure that ordering of siblings in ReceiveFilter is done in same direction!
176         Content[] childArray = (Content[]) children.toArray(new Content[children.size()]);
177         for (int i = childArray.length - 1; i >=0; i--) {
178             this.activateRecursive(node.getHandle(), childArray[i], ctx);
179         }
180     }
181 
182     /**
183      * @param ctx
184      * @param versionMap
185      * */
186     protected void activateRecursive(Context ctx, List versionMap)
187     throws ExchangeException, RepositoryException {
188         // activate all uuid's present in versionMap
189         Map<String, Object>[] versions = (Map<String, Object>[]) versionMap.toArray(new Map[0]);
190         // add path and order info into the entries
191         for (int i = 0; i < versions.length; i++) {
192             Map<String, Object> entry = versions[i];
193             String uuid = (String) entry.get("uuid");
194             if (StringUtils.equalsIgnoreCase("class", uuid)) {
195                 // TODO: this should not happen in between the serialized list, somewhere a bug
196                 // for the moment simply ignore it
197                 versionMap.remove(entry);
198             }
199             try {
200                 Content content = ctx.getHierarchyManager(getRepository()).getContentByUUID(uuid);
201                 entry.put("handle", content.getHandle());
202                 entry.put("index", i);
203             } catch (RepositoryException re) {
204                 log.error("Failed to activate node with UUID : "+uuid);
205                 log.error(re.getMessage());
206                 versionMap.remove(entry);
207             }
208         }
209         versions = null;
210 
211         // 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
212         Collections.sort((List<Map<String, Object>>) versionMap, new Comparator<Map<String, Object>>() {
213 
214             @Override
215             public int compare(Map<String, Object> o1, Map<String, Object> o2) {
216                 String handle1 = (String) o1.get("handle");
217                 String handle2 = (String) o2.get("handle");
218                 if (handle2.startsWith(handle1)) {
219                     // o2 is child of o1, say o1 is smaller to get it ordered BEFORE o2
220                     return -1;
221                 }
222                 String parent1 = StringUtils.substringBeforeLast(handle1, "/");
223                 String parent2 = StringUtils.substringBeforeLast(handle2, "/");
224                 if (parent1.equals(parent2)) {
225                     // siblings ... to reverse order, the higher index value get ordered before lower values index
226                     int idx1 = (Integer) o1.get("index");
227                     int idx2 = (Integer) o2.get("index");
228                     // index is generated in the loop above and can be never same for 2 items ... skip equality case
229                     return idx1 < idx2 ? 1 : -1;
230                 }
231 
232                 // unrelated entries, the one closer to the root should be returned first
233                 int dirLevels1 = StringUtils.countMatches(handle1, "/");
234                 int dirLevels2 = StringUtils.countMatches(handle2, "/");
235                 // since parents are checked above, the equality case here means different hierarchy of same depth and is irrelevant to activation order
236                 return dirLevels1 < dirLevels2 ? -1 : 1;
237             }});
238 
239         // FYI: need to reverse order of child activation since content ordering info is also bottom-up
240         // Hackish at best. If changing this, don't forget to also change other activateRecursive() method
241         // and most importantly ensure that ordering of siblings in ReceiveFilter is done in same direction!
242         for (Map entry : (List<Map>) versionMap) {
243 
244             String uuid = (String) entry.get("uuid");
245             String versionNumber = (String) entry.get("version");
246             if (StringUtils.equalsIgnoreCase("class", uuid)) {
247                 // TODO: this should not happen in between the serialized list, somewhere a bug
248                 // for the moment simply ignore it
249                 continue;
250             }
251             try {
252                 Content content = ctx.getHierarchyManager(getRepository()).getContentByUUID(uuid);
253                 // NOTE : on activation of the version use current hierarchy to order
254                 // since versioning operation does not preserve order anywhere
255                 List orderedList = getOrderingInfo(content);
256                 String parentPath = content.getParent().getHandle();
257                 content = content.getVersionedContent(versionNumber);
258                 // add order info for the first node as it represents the parent in a tree
259                 getSyndicator().activate(parentPath, content, orderedList);
260             } catch (RepositoryException re) {
261                 log.error("Failed to activate node with UUID : "+uuid);
262                 log.error(re.getMessage());
263             }
264         }
265     }
266 
267     /**
268      * collect node UUID of the siblings in the exact order as it should be written on
269      * subscribers
270      * @param node
271      * */
272     protected List getOrderingInfo(Content node) {
273         //do not use magnolia Content class since these objects are only meant for a single use to read UUID
274         // TODO: what's wrong with using magnolia content???
275         List siblings = new ArrayList();
276         Node thisNode = node.getJCRNode();
277         try {
278             String thisNodeType = node.getNodeTypeName();
279             String thisNodeUUID = node.getUUID();
280             NodeIterator nodeIterator = thisNode.getParent().getNodes();
281             while (nodeIterator.hasNext()) { // only collect elements after this node
282                 Node sibling = nodeIterator.nextNode();
283                 // skip till the actual position
284                 if (sibling.isNodeType(thisNodeType)) {
285                     if (thisNodeUUID.equalsIgnoreCase(sibling.getUUID())) {
286                         break;
287                     }
288                 }
289             }
290             while (nodeIterator.hasNext()) {
291                 Node sibling = nodeIterator.nextNode();
292                 if (sibling.isNodeType(thisNodeType)) {
293                     siblings.add(sibling.getUUID());
294                 }
295             }
296         } catch (RepositoryException re) {
297             // do not throw this exception, if it fails simply do not add any ordering info
298             log.error("Failed to get Ordering info", re);
299         }
300         return siblings;
301     }
302 
303     /**
304      * @return the recursive
305      */
306     public boolean isRecursive() {
307         return recursive;
308     }
309 
310     /**
311      * @param recursive the recursive to set
312      */
313     public void setRecursive(boolean recursive) {
314         this.recursive = recursive;
315     }
316 
317     /**
318      * @param number version number to be set for activation
319      * */
320     public void setVersion(String number) {
321         this.versionNumber = number;
322     }
323 
324     /**
325      * @return version number
326      * */
327     public String getVersion() {
328         return this.versionNumber;
329     }
330 
331     /**
332      * @param versionMap version map to be set for activation
333      * */
334     public void setVersionMap(List versionMap) {
335         this.versionMap = versionMap;
336     }
337 
338     /**
339      * @return version map
340      * */
341     public List getVersionMap() {
342         return this.versionMap;
343     }
344 
345     @Override
346     public void release() {
347         super.release();
348         this.versionMap = null;
349         this.recursive = false;
350         this.versionNumber = null;
351     }
352 
353 }