View Javadoc
1   /**
2    * This file Copyright (c) 2016-2018 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 static com.vaadin.server.Sizeable.Unit.*;
37  
38  import java.util.Map;
39  
40  import com.vaadin.ui.Button;
41  import com.vaadin.ui.Component;
42  import com.vaadin.ui.CssLayout;
43  import com.vaadin.v7.data.Property;
44  import com.vaadin.v7.data.fieldgroup.BeanFieldGroup;
45  import com.vaadin.v7.data.fieldgroup.FieldGroup;
46  import com.vaadin.v7.data.util.BeanItem;
47  import com.vaadin.v7.ui.CustomField;
48  import com.vaadin.v7.ui.Field;
49  import com.vaadin.v7.ui.HorizontalLayout;
50  import com.vaadin.v7.ui.NativeSelect;
51  import com.vaadin.v7.ui.TextField;
52  
53  /**
54   * A field implementation dedicated to access control entries (permission, access type and path).
55   *
56   * <p>Support for the access type, as well as chooser pluggability are optional (these only apply to workspace ACLs, not URI ACLs).
57   */
58  public class AccessControlField extends CustomField<AccessControlList.Entry> {
59  
60      private NativeSelect permissionSelect = new NativeSelect();
61      private NativeSelect accessTypeSelect;
62      private TextField path = new TextField();
63      private FieldGroup fieldGroup = new BeanFieldGroup<>(AccessControlList.Entry.class);
64  
65      private Map<Long, String> permissions;
66      private Map<Long, String> accessTypes;
67      private String chooseButtonCaption = "Choose";
68      private PathChooserHandler pathChooserHandler;
69      private ValueChangeListener valueChangeListener;
70  
71      /**
72       * Creates an AccessControlField with a permission select and a path text-field.
73       *
74       * @param permissions a map whose keys are permission values, and whose values are the corresponding captions to display in the permission select.
75       */
76      public AccessControlField(Map<Long, String> permissions) {
77          this(permissions, null);
78      }
79  
80      /**
81       * Creates an AccessControlField with a permission type select, access-type select and a path text-field.
82       *
83       * @param permissions a map whose keys are permission values, and whose values are the corresponding captions to display in the permission select.
84       * @param accessTypes a map whose keys are access type values, and whose values are the corresponding captions to display in the access type select. In the case of the web access field, this param can be ignored.
85       */
86      public AccessControlField(Map<Long, String> permissions, Map<Long, String> accessTypes) {
87          if (permissions == null || permissions.isEmpty()) {
88              throw new IllegalArgumentException("AccessControlField requires a non-empty collection of permission entries.");
89          }
90          this.permissions = permissions;
91          this.accessTypes = accessTypes;
92      }
93  
94      @Override
95      protected Component initContent() {
96          // init fields
97          permissionSelect.setNullSelectionAllowed(false);
98          permissionSelect.setInvalidAllowed(false);
99          permissionSelect.setNewItemsAllowed(false);
100         for (Map.Entry<Long, String> entry : permissions.entrySet()) {
101             permissionSelect.addItem(entry.getKey());
102             permissionSelect.setItemCaption(entry.getKey(), entry.getValue());
103         }
104 
105         if (accessTypes != null && !accessTypes.isEmpty()) {
106             accessTypeSelect = new NativeSelect();
107             accessTypeSelect.setNullSelectionAllowed(false);
108             accessTypeSelect.setInvalidAllowed(false);
109             accessTypeSelect.setNewItemsAllowed(false);
110             accessTypeSelect.setWidth(150, PIXELS);
111             for (Map.Entry<Long, String> entry : accessTypes.entrySet()) {
112                 accessTypeSelect.addItem(entry.getKey());
113                 accessTypeSelect.setItemCaption(entry.getKey(), entry.getValue());
114             }
115         }
116 
117         path.setWidth(100, PERCENTAGE);
118 
119         // bind fields + layout
120         CssLayout ruleLayout = new CssLayout();
121         ruleLayout.setWidth("100%");
122         ruleLayout.addStyleName("access-control-field");
123 
124         HorizontalLayout selectsLayout = new HorizontalLayout();
125         selectsLayout.setSpacing(true);
126         selectsLayout.addStyleName("select-fields");
127         fieldGroup.bind(permissionSelect, "permissions");
128         selectsLayout.addComponent(permissionSelect);
129 
130         if (accessTypeSelect != null) {
131             fieldGroup.bind(accessTypeSelect, "accessType");
132             selectsLayout.addComponent(accessTypeSelect);
133         }
134 
135         ruleLayout.addComponent(selectsLayout);
136 
137         HorizontalLayout pathChooserLayout = new HorizontalLayout();
138         pathChooserLayout.setSpacing(true);
139         pathChooserLayout.addStyleName("path-chooser");
140         fieldGroup.bind(path, "path");
141         pathChooserLayout.addComponent(path);
142 
143         if (pathChooserHandler != null) {
144             Button chooseButton = new Button(chooseButtonCaption, (Button.ClickListener) event -> {
145                 pathChooserHandler.openChooser(path.getPropertyDataSource());
146                 event.getButton().setEnabled(true);
147             });
148             chooseButton.setDisableOnClick(true);
149             pathChooserLayout.addComponent(chooseButton);
150         }
151 
152         ruleLayout.addComponent(pathChooserLayout);
153 
154         // make sure to propagate value changes to the underlying item without requiring a fieldGroup commit
155         fieldGroup.setBuffered(false);
156 
157         return ruleLayout;
158     }
159 
160     @Override
161     public Class<? extends AccessControlList.Entry> getType() {
162         return AccessControlList.Entry.class;
163     }
164 
165     @Override
166     protected void setInternalValue(AccessControlList.Entry newValue) {
167         super.setInternalValue(newValue);
168         fieldGroup.setItemDataSource(new BeanItem<>(newValue));
169     }
170 
171     /*
172      * Make sure to propagate inner value changes when validation is visible (i.e. field is invalid);
173      * so that corrected values are validated on the fly and eventually remove validation marks.
174      */
175     @Override
176     public void setValidationVisible(boolean validateAutomatically) {
177         super.setValidationVisible(validateAutomatically);
178 
179         if (validateAutomatically && valueChangeListener == null) {
180             valueChangeListener = (ValueChangeListener) event -> fireValueChange(false);
181             for (Field<?> field : fieldGroup.getFields()) {
182                 field.addValueChangeListener(valueChangeListener);
183             }
184         } else if (!validateAutomatically && valueChangeListener != null) {
185             for (Field<?> field : fieldGroup.getFields()) {
186                 field.removeValueChangeListener(valueChangeListener);
187             }
188             valueChangeListener = null;
189         }
190     }
191 
192     public String getChooseButtonCaption() {
193         return chooseButtonCaption;
194     }
195 
196     public void setChooseButtonCaption(String chooseButtonCaption) {
197         this.chooseButtonCaption = chooseButtonCaption;
198     }
199 
200     public PathChooserHandler getPathChooserHandler() {
201         return pathChooserHandler;
202     }
203 
204     public void setPathChooserHandler(PathChooserHandler pathChooserHandler) {
205         this.pathChooserHandler = pathChooserHandler;
206     }
207 
208     /**
209      * A hook to the path field for the current entry, in order to choose and update its path.
210      */
211     public interface PathChooserHandler {
212 
213         /**
214          * Invoked with the current entry's path property to use as a default value.
215          * Implementations are expected to set the value back to this property when done.
216          */
217         void openChooser(Property<String> pathProperty);
218     }
219 }