View Javadoc

1   /**
2    * This file Copyright (c) 2013 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.security.app.dialog.field;
35  
36  import info.magnolia.cms.security.Permission;
37  import info.magnolia.jcr.RuntimeRepositoryException;
38  import info.magnolia.objectfactory.ComponentProvider;
39  import info.magnolia.ui.api.ModelConstants;
40  import info.magnolia.ui.api.context.UiContext;
41  import info.magnolia.ui.api.overlay.OverlayCloser;
42  import info.magnolia.ui.contentapp.choosedialog.ChooseDialogPresenter;
43  import info.magnolia.ui.contentapp.choosedialog.ChooseDialogView;
44  import info.magnolia.ui.contentapp.choosedialog.WorkbenchChooseDialogPresenter;
45  import info.magnolia.ui.contentapp.choosedialog.WorkbenchChooseDialogView;
46  import info.magnolia.ui.api.app.ItemChosenListener;
47  import info.magnolia.ui.vaadin.editorlike.DialogActionListener;
48  import info.magnolia.ui.vaadin.integration.jcr.AbstractJcrNodeAdapter;
49  import info.magnolia.ui.vaadin.integration.jcr.DefaultProperty;
50  import info.magnolia.ui.vaadin.integration.jcr.JcrNewNodeAdapter;
51  import info.magnolia.ui.vaadin.integration.jcr.JcrNodeAdapter;
52  import info.magnolia.ui.workbench.column.definition.ColumnDefinition;
53  import info.magnolia.ui.workbench.column.definition.PropertyColumnDefinition;
54  import info.magnolia.ui.workbench.definition.ConfiguredNodeTypeDefinition;
55  import info.magnolia.ui.workbench.definition.ConfiguredWorkbenchDefinition;
56  import info.magnolia.ui.workbench.definition.ContentPresenterDefinition;
57  import info.magnolia.ui.workbench.definition.NodeTypeDefinition;
58  import info.magnolia.ui.workbench.definition.WorkbenchDefinition;
59  import info.magnolia.ui.workbench.tree.TreePresenterDefinition;
60  
61  import java.util.ArrayList;
62  import java.util.List;
63  
64  import javax.jcr.Node;
65  import javax.jcr.RepositoryException;
66  
67  import org.apache.commons.lang.StringUtils;
68  import org.apache.jackrabbit.JcrConstants;
69  import org.slf4j.Logger;
70  import org.slf4j.LoggerFactory;
71  
72  import com.vaadin.data.Item;
73  import com.vaadin.data.Property;
74  import com.vaadin.ui.AbstractOrderedLayout;
75  import com.vaadin.ui.Button;
76  import com.vaadin.ui.Component;
77  import com.vaadin.ui.CustomField;
78  import com.vaadin.ui.Field;
79  import com.vaadin.ui.HorizontalLayout;
80  import com.vaadin.ui.Label;
81  import com.vaadin.ui.NativeSelect;
82  import com.vaadin.ui.TextField;
83  import com.vaadin.ui.VerticalLayout;
84  
85  /**
86   * Field builder for the workspace ACL field.  Adds data to the item in an intermediary format that is transformed to the
87   * final format by {@link info.magnolia.security.app.dialog.action.SaveRoleDialogAction}.
88   *
89   * @param <D> definition type
90   * @see WorkspaceAccessFieldDefinition
91   * @see info.magnolia.security.app.dialog.action.SaveRoleDialogAction
92   */
93  public class WorkspaceAccessFieldFactory<D extends WorkspaceAccessFieldDefinition> extends AbstractAccessFieldFactory<D> {
94  
95      private static final Logger log = LoggerFactory.getLogger(WorkspaceAccessFieldFactory.class);
96  
97      public static final String INTERMEDIARY_FORMAT_PROPERTY_NAME = "__intermediary_format";
98      public static final String ACCESS_TYPE_PROPERTY_NAME = "accessType";
99  
100     private final ComponentProvider componentProvider;
101     private final UiContext uiContext;
102 
103     public WorkspaceAccessFieldFactory(D definition, Item relatedFieldItem, ComponentProvider componentProvider, UiContext uiContext) {
104         super(definition, relatedFieldItem);
105         this.componentProvider = componentProvider;
106         this.uiContext = uiContext;
107     }
108 
109     @Override
110     protected Field<Object> createFieldComponent() {
111 
112         final String aclName = "acl_" + getFieldDefinition().getWorkspace();
113 
114         final VerticalLayout layout = new VerticalLayout();
115         layout.setSpacing(true);
116 
117         try {
118 
119             final JcrNodeAdapter roleItem = (JcrNodeAdapter) item;
120             Node roleNode = roleItem.getJcrItem();
121 
122             final VerticalLayout aclLayout = new VerticalLayout();
123 
124             final Label emptyLabel = new Label("No access.");
125 
126             if (roleNode.hasNode(aclName)) {
127 
128                 final Node aclNode = roleNode.getNode(aclName);
129 
130                 AccessControlList acl = new AccessControlList();
131                 acl.readEntries(aclNode);
132 
133                 AbstractJcrNodeAdapter aclItem = new JcrNodeAdapter(aclNode);
134                 roleItem.addChild(aclItem);
135 
136                 aclItem.addItemProperty(INTERMEDIARY_FORMAT_PROPERTY_NAME, new DefaultProperty<String>(String.class, "true"));
137 
138                 for (AccessControlList.Entry entry : acl.getEntries()) {
139 
140                     long permissions = entry.getPermissions();
141                     long accessType = entry.getAccessType();
142                     String path = entry.getPath();
143 
144                     JcrNewNodeAdapter entryItem = addAclEntryItem(aclItem);
145                     entryItem.addItemProperty(INTERMEDIARY_FORMAT_PROPERTY_NAME, new DefaultProperty<String>(String.class, "true"));
146                     entryItem.addItemProperty(AccessControlList.PERMISSIONS_PROPERTY_NAME, new DefaultProperty<Long>(Long.class, permissions));
147                     entryItem.addItemProperty(ACCESS_TYPE_PROPERTY_NAME, new DefaultProperty<Long>(Long.class, accessType));
148                     entryItem.addItemProperty(AccessControlList.PATH_PROPERTY_NAME, new DefaultProperty<String>(String.class, path));
149 
150                     Component ruleRow = createRuleRow(aclLayout, entryItem, emptyLabel);
151                     aclLayout.addComponent(ruleRow);
152                 }
153             }
154 
155             if (aclLayout.getComponentCount() == 0) {
156                 aclLayout.addComponent(emptyLabel);
157             }
158 
159             final HorizontalLayout buttons = new HorizontalLayout();
160             final Button addButton = new Button("Add new");
161             addButton.addClickListener(new Button.ClickListener() {
162 
163                 @Override
164                 public void buttonClick(Button.ClickEvent event) {
165                     try {
166 
167                         AbstractJcrNodeAdapter aclItem = getOrAddAclItem(roleItem, aclName);
168                         if (aclItem.getItemProperty(INTERMEDIARY_FORMAT_PROPERTY_NAME) == null) {
169                             aclItem.addItemProperty(INTERMEDIARY_FORMAT_PROPERTY_NAME, new DefaultProperty<String>(String.class, "true"));
170                         }
171 
172                         JcrNewNodeAdapter entryItem = addAclEntryItem(aclItem);
173                         entryItem.addItemProperty(INTERMEDIARY_FORMAT_PROPERTY_NAME, new DefaultProperty<String>(String.class, "true"));
174                         entryItem.addItemProperty(AccessControlList.PERMISSIONS_PROPERTY_NAME, new DefaultProperty<Long>(Long.class, Permission.ALL));
175                         entryItem.addItemProperty(ACCESS_TYPE_PROPERTY_NAME, new DefaultProperty<Long>(Long.class, AccessControlList.ACCESS_TYPE_NODE_AND_CHILDREN));
176                         entryItem.addItemProperty(AccessControlList.PATH_PROPERTY_NAME, new DefaultProperty<String>(String.class, ""));
177 
178                         Component ruleRow = createRuleRow(aclLayout, entryItem, emptyLabel);
179                         aclLayout.removeComponent(emptyLabel);
180                         aclLayout.addComponent(ruleRow, aclLayout.getComponentCount() - 1);
181                     } catch (RepositoryException e) {
182                         throw new RuntimeRepositoryException(e);
183                     }
184                 }
185             });
186             buttons.addComponent(addButton);
187             aclLayout.addComponent(buttons);
188 
189             layout.addComponent(aclLayout);
190 
191         } catch (RepositoryException e) {
192             throw new RuntimeRepositoryException(e);
193         }
194 
195         return new CustomField<Object>() {
196 
197             @Override
198             protected Component initContent() {
199                 return layout;
200             }
201 
202             @Override
203             public Class<?> getType() {
204                 return Object.class;
205             }
206         };
207     }
208 
209     protected Component createRuleRow(final AbstractOrderedLayout parentContainer, final AbstractJcrNodeAdapter ruleItem, final Label emptyLabel) {
210 
211         final HorizontalLayout ruleLayout = new HorizontalLayout();
212         ruleLayout.setSpacing(true);
213 
214         NativeSelect accessRights = new NativeSelect();
215         accessRights.setNullSelectionAllowed(false);
216         accessRights.setImmediate(true);
217         accessRights.setInvalidAllowed(false);
218         accessRights.setNewItemsAllowed(false);
219         accessRights.addItem(Permission.ALL);
220         accessRights.setItemCaption(Permission.ALL, "Read/Write");
221         accessRights.addItem(Permission.READ);
222         accessRights.setItemCaption(Permission.READ, "Read-only");
223         accessRights.addItem(Permission.NONE);
224         accessRights.setItemCaption(Permission.NONE, "Deny access");
225         accessRights.setPropertyDataSource(ruleItem.getItemProperty(AccessControlList.PERMISSIONS_PROPERTY_NAME));
226         ruleLayout.addComponent(accessRights);
227 
228         NativeSelect accessType = new NativeSelect();
229         accessType.setNullSelectionAllowed(false);
230         accessType.setImmediate(true);
231         accessType.setInvalidAllowed(false);
232         accessType.setNewItemsAllowed(false);
233         accessType.setWidth("150px");
234         accessType.addItem(AccessControlList.ACCESS_TYPE_NODE);
235         accessType.setItemCaption(AccessControlList.ACCESS_TYPE_NODE, "Selected");
236         accessType.addItem(AccessControlList.ACCESS_TYPE_CHILDREN);
237         accessType.setItemCaption(AccessControlList.ACCESS_TYPE_CHILDREN, "Sub nodes");
238         accessType.addItem(AccessControlList.ACCESS_TYPE_NODE_AND_CHILDREN);
239         accessType.setItemCaption(AccessControlList.ACCESS_TYPE_NODE_AND_CHILDREN, "Selected and sub nodes");
240         Property accessTypeProperty = ruleItem.getItemProperty(ACCESS_TYPE_PROPERTY_NAME);
241         accessType.setPropertyDataSource(accessTypeProperty);
242         ruleLayout.addComponent(accessType);
243 
244         final TextField path = new TextField();
245         path.setWidth("125px");
246         path.setPropertyDataSource(ruleItem.getItemProperty(AccessControlList.PATH_PROPERTY_NAME));
247         ruleLayout.addComponent(path);
248 
249         Button chooseButton = new Button("Choose...");
250         chooseButton.addClickListener(new Button.ClickListener() {
251 
252             @Override
253             public void buttonClick(Button.ClickEvent event) {
254                 openChooseDialog(path);
255             }
256         });
257         ruleLayout.addComponent(chooseButton);
258 
259         Button deleteButton = new Button();
260         deleteButton.setHtmlContentAllowed(true);
261         deleteButton.setCaption("<span class=\"" + "icon-trash" + "\"></span>");
262         deleteButton.addStyleName("inline");
263         deleteButton.setDescription("Delete");
264         deleteButton.addClickListener(new Button.ClickListener() {
265 
266             @Override
267             public void buttonClick(Button.ClickEvent event) {
268                 parentContainer.removeComponent(ruleLayout);
269                 ruleItem.getParent().removeChild(ruleItem);
270                 if (parentContainer.getComponentCount() == 1) {
271                     parentContainer.addComponent(emptyLabel, 0);
272                 }
273             }
274         });
275         ruleLayout.addComponent(deleteButton);
276 
277 
278         return ruleLayout;
279     }
280 
281     protected void openChooseDialog(final TextField textField) {
282 
283         final ItemChosenListener listener = new ItemChosenListener() {
284 
285             @Override
286             public void onItemChosen(Item item) {
287                 try {
288                     textField.setValue(((AbstractJcrNodeAdapter) item).getJcrItem().getPath());
289                 } catch (RepositoryException e) {
290                     log.error("Failed to read chosen node", e);
291                 }
292             }
293 
294             @Override
295             public void onChooseCanceled() {
296             }
297         };
298 
299         final WorkbenchChooseDialogPresenter workbenchChooseDialogPresenter = componentProvider.newInstance(WorkbenchChooseDialogPresenter.class);
300         workbenchChooseDialogPresenter.setWorkbenchDefinition(resolveWorkbenchDefinition());
301 
302         workbenchChooseDialogPresenter.addActionCallback(WorkbenchChooseDialogView.COMMIT_ACTION_NAME, new DialogActionListener() {
303             @Override
304             public void onActionExecuted(final String actionName) {
305                 listener.onItemChosen(workbenchChooseDialogPresenter.getValue());
306             }
307         });
308 
309         workbenchChooseDialogPresenter.addActionCallback(WorkbenchChooseDialogView.CANCEL_ACTION_NAME, new DialogActionListener() {
310             @Override
311             public void onActionExecuted(final String actionName) {
312                 listener.onChooseCanceled();
313             }
314         });
315 
316         ChooseDialogView chooseDialogView = workbenchChooseDialogPresenter.start();
317         chooseDialogView.setCaption(StringUtils.capitalize(getFieldDefinition().getWorkspace()));
318 
319         final OverlayCloser overlayCloser = uiContext.openOverlay(chooseDialogView);
320 
321         workbenchChooseDialogPresenter.setListener(new ChooseDialogPresenter.Listener() {
322 
323             @Override
324             public void onClose() {
325                 overlayCloser.close();
326             }
327         });
328     }
329 
330     protected WorkbenchDefinition resolveWorkbenchDefinition() {
331 
332         if (getFieldDefinition().getWorkbench() != null) {
333             return getFieldDefinition().getWorkbench();
334         }
335 
336         ConfiguredWorkbenchDefinition workbenchDefinition = new ConfiguredWorkbenchDefinition();
337         workbenchDefinition.setWorkspace(getFieldDefinition().getWorkspace());
338         workbenchDefinition.setPath("/");
339         workbenchDefinition.setDialogWorkbench(true);
340         workbenchDefinition.setEditable(false);
341         workbenchDefinition.setDefaultOrder(ModelConstants.JCR_NAME);
342 
343         // node types
344         workbenchDefinition.setNodeTypes(resolveNodeTypes());
345 
346         // content views
347         ArrayList<ContentPresenterDefinition> contentViews = new ArrayList<ContentPresenterDefinition>();
348         TreePresenterDefinition treeView = new TreePresenterDefinition();
349         ArrayList<ColumnDefinition> columns = new ArrayList<ColumnDefinition>();
350         PropertyColumnDefinition column = new PropertyColumnDefinition();
351         column.setEditable(false);
352         column.setDisplayInChooseDialog(true);
353         column.setLabel("Node name");
354         column.setPropertyName(ModelConstants.JCR_NAME);
355         column.setName(ModelConstants.JCR_NAME);
356         columns.add(column);
357         treeView.setColumns(columns);
358         contentViews.add(treeView);
359         workbenchDefinition.setContentViews(contentViews);
360 
361         return workbenchDefinition;
362     }
363 
364     private List<NodeTypeDefinition> resolveNodeTypes() {
365 
366         if (getFieldDefinition().getNodeTypes() != null) {
367             return getFieldDefinition().getNodeTypes();
368         }
369 
370         ArrayList<NodeTypeDefinition> nodeTypes = new ArrayList<NodeTypeDefinition>();
371         ConfiguredNodeTypeDefinition nodeType = new ConfiguredNodeTypeDefinition();
372         nodeType.setName(JcrConstants.NT_BASE);
373         nodeType.setIcon("icon-folder");
374         nodeTypes.add(nodeType);
375 
376         return nodeTypes;
377     }
378 }