fix #7574 adding the ToOneRelatedEntityReloader to the TeamOrPerson and Person field...
[cdm-vaadin.git] / src / main / java / eu / etaxonomy / cdm / vaadin / component / common / TeamOrPersonField.java
1 /**
2 * Copyright (C) 2017 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
5 *
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
8 */
9 package eu.etaxonomy.cdm.vaadin.component.common;
10
11 import java.util.Arrays;
12 import java.util.EnumSet;
13 import java.util.List;
14
15 import org.vaadin.viritin.fields.LazyComboBox;
16
17 import com.vaadin.data.Validator.InvalidValueException;
18 import com.vaadin.data.fieldgroup.BeanFieldGroup;
19 import com.vaadin.data.fieldgroup.FieldGroup;
20 import com.vaadin.data.util.BeanItem;
21 import com.vaadin.server.FontAwesome;
22 import com.vaadin.server.UserError;
23 import com.vaadin.shared.AbstractFieldState;
24 import com.vaadin.ui.Button;
25 import com.vaadin.ui.Component;
26 import com.vaadin.ui.CssLayout;
27 import com.vaadin.ui.Field;
28 import com.vaadin.ui.themes.ValoTheme;
29
30 import eu.etaxonomy.cdm.api.utility.UserHelper;
31 import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
32 import eu.etaxonomy.cdm.model.agent.AgentBase;
33 import eu.etaxonomy.cdm.model.agent.Person;
34 import eu.etaxonomy.cdm.model.agent.Team;
35 import eu.etaxonomy.cdm.model.agent.TeamOrPersonBase;
36 import eu.etaxonomy.cdm.persistence.hibernate.permission.CRUD;
37 import eu.etaxonomy.cdm.service.CdmFilterablePagingProvider;
38 import eu.etaxonomy.cdm.service.UserHelperAccess;
39 import eu.etaxonomy.cdm.vaadin.component.ButtonFactory;
40 import eu.etaxonomy.cdm.vaadin.event.ToOneRelatedEntityReloader;
41 import eu.etaxonomy.cdm.vaadin.util.TeamOrPersonBaseCaptionGenerator;
42 import eu.etaxonomy.cdm.vaadin.util.converter.CdmBaseDeproxyConverter;
43 import eu.etaxonomy.cdm.vaadin.view.name.CachingPresenter;
44 import eu.etaxonomy.vaadin.component.CompositeCustomField;
45 import eu.etaxonomy.vaadin.component.EntityFieldInstantiator;
46 import eu.etaxonomy.vaadin.component.ReloadableLazyComboBox;
47 import eu.etaxonomy.vaadin.component.SwitchableTextField;
48 import eu.etaxonomy.vaadin.component.ToManyRelatedEntitiesListSelect;
49
50 /**
51 * @author a.kohlbecker
52 * @since May 11, 2017
53 *
54 */
55 public class TeamOrPersonField extends CompositeCustomField<TeamOrPersonBase<?>> {
56
57 private static final long serialVersionUID = 660806402243118112L;
58
59 private static final String PRIMARY_STYLE = "v-team-or-person-field";
60
61 private CssLayout root = new CssLayout();
62 private CssLayout toolBar= new CssLayout();
63 private CssLayout compositeWrapper = new CssLayout();
64
65 private ReloadableLazyComboBox<TeamOrPersonBase> teamOrPersonSelect = new ReloadableLazyComboBox<TeamOrPersonBase>(TeamOrPersonBase.class);
66
67 private Button removeButton = ButtonFactory.REMOVE_ALL_ITEMS.createButton();
68 private Button personButton = new Button(FontAwesome.USER);
69 private Button teamButton = new Button(FontAwesome.USERS);
70
71 // Fields for case when value is a Person
72 private PersonField personField = new PersonField();
73
74 // Fields for case when value is a Team
75 private SwitchableTextField titleField = new SwitchableTextField("Team (bibliographic)");
76 private SwitchableTextField nomenclaturalTitleField = new SwitchableTextField("Team (nomenclatural)");
77 private ToManyRelatedEntitiesListSelect<Person, PersonField> personsListEditor = new ToManyRelatedEntitiesListSelect<Person, PersonField>(Person.class, PersonField.class, "Team members");
78
79 private BeanFieldGroup<Team> fieldGroup = new BeanFieldGroup<>(Team.class);
80
81 private TeamOrPersonBaseCaptionGenerator.CacheType cacheType;
82
83 protected List<Component> editorComponents = Arrays.asList(removeButton, personButton, teamButton, teamOrPersonSelect);
84
85 public TeamOrPersonField(String caption, TeamOrPersonBaseCaptionGenerator.CacheType cacheType){
86
87 setCaption(caption);
88
89 this.cacheType = cacheType;
90 teamOrPersonSelect.setCaptionGenerator(new TeamOrPersonBaseCaptionGenerator<TeamOrPersonBase>(cacheType));
91
92
93 addStyledComponent(teamOrPersonSelect);
94 addStyledComponent(personField);
95 addStyledComponent(titleField);
96 addStyledComponent(nomenclaturalTitleField);
97 addStyledComponent(personsListEditor);
98 addStyledComponents(removeButton, personButton, teamButton);
99
100
101 addSizedComponent(root);
102 addSizedComponent(compositeWrapper);
103 addSizedComponent(personField);
104 addSizedComponent(titleField);
105 addSizedComponent(nomenclaturalTitleField);
106 addSizedComponent(personsListEditor);
107
108 setConverter(new CdmBaseDeproxyConverter<TeamOrPersonBase<?>>());
109
110 updateToolBarButtonStates();
111 }
112
113 /**
114 * {@inheritDoc}
115 */
116 @Override
117 protected Component initContent() {
118
119 teamOrPersonSelect.addValueChangeListener(e -> {
120 setValue(teamOrPersonSelect.getValue(), false, true);
121 });
122 teamOrPersonSelect.setWidthUndefined();
123
124 removeButton.addClickListener(e -> {
125 teamOrPersonSelect.clear();
126 setValue(null, false, true);
127 });
128 removeButton.setDescription("Remove");
129
130 personButton.addClickListener(e -> {
131 setValue(Person.NewInstance(), false, true);
132 resetReadOnlyComponents();
133
134 });
135 personButton.setDescription("Add person");
136 teamButton.addClickListener(e -> {
137 setValue(Team.NewInstance(), false, true);
138 resetReadOnlyComponents();
139 });
140 teamButton.setDescription("Add team");
141
142 toolBar.setStyleName(ValoTheme.LAYOUT_COMPONENT_GROUP + " toolbar");
143 toolBar.addComponents(teamOrPersonSelect, removeButton, personButton, teamButton);
144
145 compositeWrapper.setStyleName("margin-wrapper");
146 compositeWrapper.addComponent(toolBar);
147
148 root.setPrimaryStyleName(PRIMARY_STYLE);
149 root.addComponent(compositeWrapper);
150 return root;
151 }
152
153 /**
154 * {@inheritDoc}
155 */
156 @Override
157 public Class getType() {
158 return TeamOrPersonBase.class;
159 }
160
161 private void updateToolBarButtonStates(){
162
163 TeamOrPersonBase<?> val = getInternalValue();
164 boolean userCanCreate = UserHelperAccess.userHelper().userHasPermission(Person.class, "CREATE");
165
166 teamOrPersonSelect.setVisible(val == null);
167 removeButton.setVisible(val != null);
168 personButton.setEnabled(userCanCreate && val == null);
169 teamButton.setEnabled(userCanCreate && val == null);
170 }
171
172 /**
173 * {@inheritDoc}
174 */
175 @Override
176 protected void setInternalValue(TeamOrPersonBase<?> newValue) {
177
178 TeamOrPersonBase<?> oldValue = getValue();
179 super.setInternalValue(newValue);
180
181 newValue = HibernateProxyHelper.deproxy(newValue);
182
183 compositeWrapper.removeAllComponents();
184 compositeWrapper.addComponent(toolBar);
185
186 if(newValue != null) {
187
188 if(Person.class.isAssignableFrom(newValue.getClass())){
189 // value is a Person
190 compositeWrapper.addComponent(personField);
191
192 personField.setValue((Person) newValue);
193 personField.registerParentFieldGroup(fieldGroup);
194
195 }
196 else if(Team.class.isAssignableFrom(newValue.getClass())){
197 // otherwise it a Team
198 compositeWrapper.addComponents(titleField, nomenclaturalTitleField, personsListEditor);
199
200 titleField.bindTo(fieldGroup, "titleCache", "protectedTitleCache");
201 nomenclaturalTitleField.bindTo(fieldGroup, "nomenclaturalTitle", "protectedNomenclaturalTitleCache");
202 fieldGroup.setItemDataSource(new BeanItem<Team>((Team)newValue));
203 boolean readonlyState = personsListEditor.isReadOnly();
204 fieldGroup.bind(personsListEditor, "teamMembers"); // here personField is set readonly since setTeamMembers does not exist
205 personsListEditor.setReadOnly(readonlyState); // fixing the readonly state
206
207 personsListEditor.registerParentFieldGroup(fieldGroup);
208
209 } else {
210 setComponentError(new UserError("TeamOrPersonField Error: Unsupported value type: " + newValue.getClass().getName()));
211 }
212 } else {
213 if(oldValue != null){
214 // value is null --> clean up all nested fields
215 // allow replacing old content in the editor by null
216 setReadOnlyComponents(false);
217 if(oldValue instanceof Person){
218 personField.unregisterParentFieldGroup(fieldGroup);
219 personField.setReadOnly(false);
220 personField.setValue((Person) null);
221 } else {
222 titleField.unbindFrom(fieldGroup);
223 nomenclaturalTitleField.unbindFrom(fieldGroup);
224 fieldGroup.unbind(personsListEditor);
225 fieldGroup.setItemDataSource((Team)null);
226 personsListEditor.registerParentFieldGroup(null);
227 personsListEditor.setReadOnly(false);
228 personsListEditor.setValue(null);
229 personsListEditor.registerParentFieldGroup(null);
230 }
231 }
232 }
233 adaptToUserPermissions(newValue);
234 updateToolBarButtonStates();
235 }
236
237
238 private void adaptToUserPermissions(TeamOrPersonBase teamOrPerson) {
239
240 UserHelper userHelper = UserHelperAccess.userHelper();
241 boolean canEdit = teamOrPerson == null || !teamOrPerson.isPersited() || userHelper.userHasPermission(teamOrPerson, CRUD.UPDATE);
242 if(!canEdit){
243 getPropertyDataSource().setReadOnly(true);
244 setReadOnlyComponents(true);
245 }
246 }
247
248
249 /**
250 * {@inheritDoc}
251 */
252 @Override
253 protected void addDefaultStyles() {
254 // no default styles here
255 }
256
257 /**
258 * {@inheritDoc}
259 */
260 @Override
261 public FieldGroup getFieldGroup() {
262 return fieldGroup;
263 }
264
265 public Component[] getCachFields(){
266 return new Component[]{titleField, nomenclaturalTitleField};
267 }
268
269 /**
270 * @return the teamOrPersonSelect
271 */
272 public LazyComboBox<TeamOrPersonBase> getTeamOrPersonSelect() {
273 return teamOrPersonSelect;
274 }
275
276 /**
277 * {@inheritDoc}
278 */
279 @SuppressWarnings("unchecked")
280 @Override
281 public void commit() throws SourceException, InvalidValueException {
282
283 //need to commit the subfields propagation through the fielGroups is not enough
284 personField.commit();
285 personsListEditor.commit();
286 if(!getState(false).readOnly && getPropertyDataSource().isReadOnly()){
287 // the TeamOrPersonBase Editor (remove, addPerson, addTeam) is not readonly
288 // thus removing the TeamOrPerson is allowed. In case the datasource is readonly
289 // due to missing user grants for the TeamOrPerson it must be set to readWrite to
290 // make it possible to change the property of the parent
291 getPropertyDataSource().setReadOnly(false);
292 }
293
294 super.commit();
295
296 if(hasNullContent()){
297 getPropertyDataSource().setValue(null);
298 setValue(null);
299 }
300
301 TeamOrPersonBase<?> bean = getValue();
302 if(bean != null && bean instanceof Team){
303 boolean isUnsaved = bean.getId() == 0;
304 if(isUnsaved){
305 UserHelperAccess.userHelper().createAuthorityForCurrentUser(bean, EnumSet.of(CRUD.UPDATE, CRUD.DELETE), null);
306 }
307 }
308 }
309
310 /**
311 * {@inheritDoc}
312 */
313 @Override
314 protected List<Field> nullValueCheckIgnoreFields() {
315
316 List<Field> ignoreFields = super.nullValueCheckIgnoreFields();
317 ignoreFields.add(personField);
318 ignoreFields.add(nomenclaturalTitleField.getUnlockSwitch());
319 if(nomenclaturalTitleField.getUnlockSwitch().getValue().booleanValue() == false){
320 ignoreFields.add(nomenclaturalTitleField.getTextField());
321 }
322 ignoreFields.add(titleField.getUnlockSwitch());
323 if(titleField.getUnlockSwitch().getValue().booleanValue() == false){
324 ignoreFields.add(titleField.getTextField());
325 }
326 return ignoreFields;
327 }
328
329 /**
330 * {@inheritDoc}
331 */
332 @Override
333 public boolean hasNullContent() {
334
335 TeamOrPersonBase<?> bean = getValue();
336 if(bean == null) {
337 return true;
338 }
339 if(bean instanceof Team){
340 // --- Team
341 return super.hasNullContent();
342 } else {
343 // --- Person
344 return personField.hasNullContent();
345 }
346 }
347
348 public void setFilterableTeamPagingProvider(CdmFilterablePagingProvider<AgentBase, TeamOrPersonBase> pagingProvider, CachingPresenter cachingPresenter){
349 teamOrPersonSelect.loadFrom(pagingProvider, pagingProvider, pagingProvider.getPageSize());
350 // NOTE:
351 // it is important to add the ToOneRelatedEntityReloader to the TeamOrPersonField directly
352 // since the value of the select will be immediately passed to the TeamOrPersonField
353 ToOneRelatedEntityReloader<TeamOrPersonBase<?>> teamOrPersonReloader = new ToOneRelatedEntityReloader<TeamOrPersonBase<?>>(this, cachingPresenter);
354 this.addValueChangeListener(teamOrPersonReloader);
355 }
356
357 public void setFilterablePersonPagingProvider(CdmFilterablePagingProvider<AgentBase, Person> pagingProvider, CachingPresenter cachingPresenter){
358
359 personsListEditor.setEntityFieldInstantiator(new EntityFieldInstantiator<PersonField>() {
360
361 @Override
362 public PersonField createNewInstance() {
363 PersonField f = new PersonField();
364 f.setAllowNewEmptyEntity(true); // otherwise new entities can not be added to the personsListEditor
365 f.getPersonSelect().loadFrom(pagingProvider, pagingProvider, pagingProvider.getPageSize());
366 f.getPersonSelect().setCaptionGenerator(new TeamOrPersonBaseCaptionGenerator<Person>(cacheType));
367 // NOTE:
368 // it is important to add the ToOneRelatedEntityReloader to the PersonField directly
369 // since the value of the select will be immediately passed to the PersonField:
370 f.addValueChangeListener(new ToOneRelatedEntityReloader<Person>(f, cachingPresenter));
371 return f;
372 }
373 });
374 }
375
376 @Override
377 public void setValue(TeamOrPersonBase<?> newFieldValue) {
378 // ignore readonly states of the datasource
379 setValue(newFieldValue, false, !hasSharedStateReadOnly());
380 }
381
382 protected boolean hasSharedStateReadOnly(){
383 AbstractFieldState sharedState = getState(false);
384 return sharedState.readOnly;
385 }
386
387
388 /**
389 * {@inheritDoc}
390 */
391 @Override
392 public void setReadOnly(boolean readOnly) {
393 // super.setReadOnly(readOnly); // moved into setEditorReadOnly()
394 setReadOnlyComponents(readOnly);
395 }
396
397 public void setEditorReadOnly(boolean readOnly) {
398 super.setReadOnly(readOnly);
399 for(Component c : editorComponents){
400 applyReadOnlyState(c, readOnly);
401 }
402
403 }
404
405 /**
406 * Reset the readonly state of nested components to <code>false</code>.
407 */
408 protected void resetReadOnlyComponents() {
409 if(!isReadOnly()){
410 setReadOnlyComponents(false);
411 }
412 }
413
414 /**
415 * Set the nested components (team or person fields) to read only but
416 * keep the state of the <code>TeamOrPersonField</code> untouched so
417 * that the <code>teamOrPersonSelect</code>, <code>removeButton</code>,
418 * <code>personButton</code> and <code>teamButton</code> stay operational.
419 *
420 * @param readOnly
421 */
422 protected void setReadOnlyComponents(boolean readOnly) {
423 setDeepReadOnly(readOnly, getContent(), editorComponents);
424 updateCaptionReadonlyNotice(readOnly);
425 }
426
427
428
429 }