View Javadoc
1   /**
2    * This file Copyright (c) 2016-2017 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.pages.app.editor;
35  
36  import info.magnolia.cms.core.Path;
37  import info.magnolia.context.Context;
38  import info.magnolia.jcr.util.NodeTypes;
39  import info.magnolia.jcr.util.NodeTypes.Renderable;
40  import info.magnolia.ui.framework.ContentClipboard;
41  import info.magnolia.ui.framework.ContentClipboardException;
42  import info.magnolia.ui.vaadin.gwt.client.shared.AbstractElement;
43  import info.magnolia.ui.vaadin.gwt.client.shared.AreaElement;
44  import info.magnolia.ui.vaadin.gwt.client.shared.ComponentElement;
45  
46  import java.text.MessageFormat;
47  import java.util.ArrayList;
48  import java.util.Arrays;
49  import java.util.List;
50  
51  import javax.inject.Inject;
52  import javax.inject.Provider;
53  import javax.jcr.ItemNotFoundException;
54  import javax.jcr.Node;
55  import javax.jcr.NodeIterator;
56  import javax.jcr.PathNotFoundException;
57  import javax.jcr.RepositoryException;
58  import javax.jcr.Session;
59  
60  import org.apache.commons.lang3.StringUtils;
61  import org.slf4j.Logger;
62  import org.slf4j.LoggerFactory;
63  
64  /**
65   * Subapp-scoped singleton for holding information about
66   * copied components.
67   */
68  public class ComponentContentClipboard implements ContentClipboard<AbstractElement> {
69  
70      private static final Logger log = LoggerFactory.getLogger(ComponentContentClipboard.class);
71  
72      private static final String AVAILABLE_COMPONENTS_DIVIDER = ",";
73  
74      private final Provider<Context> contextProvider;
75      private List<AbstractElement> items = new ArrayList<>();
76  
77      @Inject
78      public ComponentContentClipboard(Provider<Context> contextProvider) {
79          this.contextProvider = contextProvider;
80      }
81  
82      @Override
83      public void copy(final List<AbstractElement> source) {
84          List<AbstractElement> newItems = new ArrayList<>();
85          for (AbstractElement sourceItem : source) {
86              try {
87                  Session session = contextProvider.get().getJCRSession(sourceItem.getWorkspace());
88  
89                  if (sourceItem instanceof ComponentElement) {
90                      newItems.add(sourceItem);
91                  } else if (sourceItem instanceof AreaElement) {
92                      // all the components of a selected area are copied
93                      Node areaNode = session.getNode(sourceItem.getPath());
94                      NodeIterator nit = areaNode.getNodes();
95                      while (nit.hasNext()) {
96                          Node componentNode = nit.nextNode();
97                          if (StringUtils.equals(componentNode.getPrimaryNodeType().getName(), NodeTypes.Component.NAME)) {
98                              newItems.add(new ComponentElement(componentNode.getSession().getWorkspace().getName(), componentNode.getPath(), null));
99                          }
100                     }
101                 }
102             } catch (RepositoryException e) {
103                 throw new ContentClipboardException(MessageFormat.format("Failed while copying element [{0}] from [{1}] workspace.", sourceItem.getPath(), sourceItem.getWorkspace()), e);
104             }
105         }
106         this.items = newItems;
107     }
108 
109     /**
110      * Copies the item in clipboard under the area with provided itemId.
111      */
112     @Override
113     public List<AbstractElement> paste(final AbstractElement destination) {
114         List<AbstractElement> pastedElements = new ArrayList<>();
115 
116         try {
117             final Session session = contextProvider.get().getJCRSession(destination.getWorkspace());
118             final Node areaNode = session.getNode(destination.getPath());
119             for (AbstractElement element : items) {
120                 Node componentNode = session.getNode(element.getPath());
121                 AbstractElement duplicatedElement = copyComponent(areaNode, componentNode);
122                 pastedElements.add(duplicatedElement);
123             }
124         } catch (RepositoryException e) {
125             throw new ContentClipboardException(MessageFormat.format("Failed while pasting content of clipboard under element [{0}] from [{1}] workspace.", destination.getPath(), destination.getWorkspace()), e);
126         }
127         return pastedElements;
128     }
129 
130     @Override
131     public boolean canCopy(final List<AbstractElement> source) {
132         try {
133             boolean result = true;
134             for (AbstractElement element : source) {
135                 Session session = contextProvider.get().getJCRSession(element.getWorkspace());
136 
137                 if (element instanceof AreaElement) {
138                     Node areaNode = session.getNode(element.getPath());
139                     result = result && hasOnlyChildComponents(areaNode);
140 
141                     // break on first false
142                     if (!result) {
143                         return false;
144                     }
145                 }
146             }
147             return result;
148         } catch (RepositoryException e) {
149             log.warn("Problem while checking whether an item can be copied.", e);
150             return false;
151         }
152     }
153 
154     @Override
155     public boolean canPasteInto(final AbstractElement destination) {
156         try {
157             Session session = contextProvider.get().getJCRSession(destination.getWorkspace());
158             boolean result = items.size() > 0;
159             if (destination instanceof AreaElement) {
160                 List<String> availableComponents = Arrays.asList(((AreaElement) destination).getAvailableComponents().split(AVAILABLE_COMPONENTS_DIVIDER));
161                 for (AbstractElement source : items) {
162                     if (source instanceof ComponentElement) {
163                         Node componentNode = session.getNode(source.getPath());
164                         String templateId = Renderable.getTemplate(componentNode);
165                         result = result && availableComponents.contains(templateId);
166 
167                         // break on first false
168                         if (!result) {
169                             return false;
170                         }
171                     }
172                 }
173                 return result;
174             }
175         } catch (PathNotFoundException | ItemNotFoundException e) {
176             log.warn("Problem while checking availability. One of the copied components was deleted.", e);
177         } catch (RepositoryException e) {
178             log.warn("Problem while checking availability.", e);
179         }
180         return false;
181     }
182 
183     private AbstractElement copyComponent(Node areaNode, Node componentNode) throws RepositoryException {
184         final String newName = getUniqueNewItemName(areaNode, componentNode.getName());
185         final String newPath = Path.getAbsolutePath(areaNode.getPath(), newName);
186         // Duplicate node
187         componentNode.getSession().getWorkspace().copy(componentNode.getPath(), newPath);
188         // Update metadata
189         final Node duplicateNode = componentNode.getSession().getNode(newPath);
190         return new ComponentElement(duplicateNode.getSession().getWorkspace().getName(), duplicateNode.getPath(), null);
191     }
192 
193     private String getUniqueNewItemName(Node parent, String name) throws RepositoryException {
194         return Path.getUniqueLabel(parent.getSession(), parent.getPath(), name);
195     }
196 
197     /**
198      * Returns true if the area is not empty and all it's child nodes are components.
199      */
200     private boolean hasOnlyChildComponents(Node areaNode) throws RepositoryException {
201         boolean result = false;
202         NodeIterator nit = areaNode.getNodes();
203         if (nit.hasNext()) {
204             result = true;
205             while (nit.hasNext()) {
206                 Node node = nit.nextNode();
207                 if (!NodeTypes.Component.NAME.equals(node.getPrimaryNodeType().getName())) {
208                     result = false;
209                     break;
210                 }
211             }
212         }
213         return result;
214     }
215 
216 }