View Javadoc
1   /**
2    * This file Copyright (c) 2003-2014 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.getJCRNode());
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                 if (o1.equals(o2)) {
217                     return 0;
218                 }
219 
220                 String handle1 = (String) o1.get("handle");
221                 String handle2 = (String) o2.get("handle");
222                 if (handle1.equals(handle2)) {
223                     return 0;
224                 }
225                 if (handle2.startsWith(handle1)) {
226                     // o2 is child of o1, say o1 is smaller to get it ordered BEFORE o2
227                     return -1;
228                 }
229                 String parent1 = StringUtils.substringBeforeLast(handle1, "/");
230                 String parent2 = StringUtils.substringBeforeLast(handle2, "/");
231                 if (parent1.equals(parent2)) {
232                     // siblings ... to reverse order, the higher index value get ordered before lower values index
233                     int idx1 = (Integer) o1.get("index");
234                     int idx2 = (Integer) o2.get("index");
235                     // index is generated in the loop above and can be never same for 2 items ...
236                     if (idx1 == idx2) {
237                         return 0;
238                     }
239                     return idx1 < idx2 ? 1 : -1;
240                 }
241 
242                 // unrelated entries, the one closer to the root should be returned first
243                 int dirLevels1 = StringUtils.countMatches(handle1, "/");
244                 int dirLevels2 = StringUtils.countMatches(handle2, "/");
245                 // since parents are checked above, the equality case here means different hierarchy of same depth and is irrelevant to activation order
246                 if (dirLevels1 == dirLevels2) {
247                     return 0;
248                 }
249                 return dirLevels1 < dirLevels2 ? -1 : 1;
250             }});
251 
252         // FYI: need to reverse order of child activation since content ordering info is also bottom-up
253         // Hackish at best. If changing this, don't forget to also change other activateRecursive() method
254         // and most importantly ensure that ordering of siblings in ReceiveFilter is done in same direction!
255         for (Map entry : (List<Map>) versionMap) {
256 
257             String uuid = (String) entry.get("uuid");
258             String versionNumber = (String) entry.get("version");
259             if (StringUtils.equalsIgnoreCase("class", uuid)) {
260                 // TODO: this should not happen in between the serialized list, somewhere a bug
261                 // for the moment simply ignore it
262                 continue;
263             }
264             try {
265                 Content content = ctx.getHierarchyManager(getRepository()).getContentByUUID(uuid);
266                 // NOTE : on activation of the version use current hierarchy to order
267                 // since versioning operation does not preserve order anywhere
268                 List orderedList = getOrderingInfo(content);
269                 String parentPath = content.getParent().getHandle();
270                 content = content.getVersionedContent(versionNumber);
271                 // add order info for the first node as it represents the parent in a tree
272                 getSyndicator().activate(parentPath, content, orderedList);
273             } catch (RepositoryException re) {
274                 log.error("Failed to activate node with UUID : "+uuid);
275                 log.error(re.getMessage());
276             }
277         }
278     }
279 
280     /**
281      * collect node UUID of the siblings in the exact order as it should be written on
282      * subscribers
283      * @param node
284      * */
285     protected List getOrderingInfo(Content node) {
286         //do not use magnolia Content class since these objects are only meant for a single use to read UUID
287         // TODO: what's wrong with using magnolia content???
288         List siblings = new ArrayList();
289         Node thisNode = node.getJCRNode();
290         try {
291             String thisNodeType = node.getNodeTypeName();
292             String thisNodeUUID = node.getUUID();
293             NodeIterator nodeIterator = thisNode.getParent().getNodes();
294             while (nodeIterator.hasNext()) { // only collect elements after this node
295                 Node sibling = nodeIterator.nextNode();
296                 // skip till the actual position
297                 if (sibling.isNodeType(thisNodeType)) {
298                     if (thisNodeUUID.equalsIgnoreCase(sibling.getUUID())) {
299                         break;
300                     }
301                 }
302             }
303             while (nodeIterator.hasNext()) {
304                 Node sibling = nodeIterator.nextNode();
305                 if (sibling.isNodeType(thisNodeType)) {
306                     siblings.add(sibling.getUUID());
307                 }
308             }
309         } catch (RepositoryException re) {
310             // do not throw this exception, if it fails simply do not add any ordering info
311             log.error("Failed to get Ordering info", re);
312         }
313         return siblings;
314     }
315 
316     /**
317      * @return the recursive
318      */
319     public boolean isRecursive() {
320         return recursive;
321     }
322 
323     /**
324      * @param recursive the recursive to set
325      */
326     public void setRecursive(boolean recursive) {
327         this.recursive = recursive;
328     }
329 
330     /**
331      * @param number version number to be set for activation
332      * */
333     public void setVersion(String number) {
334         this.versionNumber = number;
335     }
336 
337     /**
338      * @return version number
339      * */
340     public String getVersion() {
341         return this.versionNumber;
342     }
343 
344     /**
345      * @param versionMap version map to be set for activation
346      * */
347     public void setVersionMap(List versionMap) {
348         this.versionMap = versionMap;
349     }
350 
351     /**
352      * @return version map
353      * */
354     public List getVersionMap() {
355         return this.versionMap;
356     }
357 
358     @Override
359     public void release() {
360         super.release();
361         this.versionMap = null;
362         this.recursive = false;
363         this.versionNumber = null;
364     }
365 
366 }