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