Project

General

Profile

Download (16.3 KB) Statistics
| Branch: | Tag: | Revision:
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
import java.util.Optional;
15

    
16
import org.apache.log4j.Logger;
17
import org.vaadin.viritin.fields.LazyComboBox;
18

    
19
import com.vaadin.data.Validator.InvalidValueException;
20
import com.vaadin.data.fieldgroup.BeanFieldGroup;
21
import com.vaadin.data.fieldgroup.FieldGroup;
22
import com.vaadin.data.util.BeanItem;
23
import com.vaadin.server.FontAwesome;
24
import com.vaadin.server.UserError;
25
import com.vaadin.shared.AbstractFieldState;
26
import com.vaadin.ui.Button;
27
import com.vaadin.ui.Component;
28
import com.vaadin.ui.CssLayout;
29
import com.vaadin.ui.Field;
30
import com.vaadin.ui.themes.ValoTheme;
31

    
32
import eu.etaxonomy.cdm.api.util.UserHelper;
33
import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
34
import eu.etaxonomy.cdm.model.agent.AgentBase;
35
import eu.etaxonomy.cdm.model.agent.Person;
36
import eu.etaxonomy.cdm.model.agent.Team;
37
import eu.etaxonomy.cdm.model.agent.TeamOrPersonBase;
38
import eu.etaxonomy.cdm.model.permission.CRUD;
39
import eu.etaxonomy.cdm.service.CdmFilterablePagingProvider;
40
import eu.etaxonomy.cdm.service.UserHelperAccess;
41
import eu.etaxonomy.cdm.vaadin.component.ButtonFactory;
42
import eu.etaxonomy.cdm.vaadin.event.ToOneRelatedEntityReloader;
43
import eu.etaxonomy.cdm.vaadin.util.TeamOrPersonBaseCaptionGenerator;
44
import eu.etaxonomy.cdm.vaadin.util.converter.CdmBaseDeproxyConverter;
45
import eu.etaxonomy.cdm.vaadin.view.name.CachingPresenter;
46
import eu.etaxonomy.vaadin.component.CompositeCustomField;
47
import eu.etaxonomy.vaadin.component.EntityFieldInstantiator;
48
import eu.etaxonomy.vaadin.component.ReloadableLazyComboBox;
49
import eu.etaxonomy.vaadin.component.SwitchableTextField;
50
import eu.etaxonomy.vaadin.component.ToManyRelatedEntitiesListSelect;
51

    
52
/**
53
 * @author a.kohlbecker
54
 * @since May 11, 2017
55
 *
56
 */
57
public class TeamOrPersonField extends CompositeCustomField<TeamOrPersonBase<?>> {
58

    
59
    private static final long serialVersionUID = 660806402243118112L;
60

    
61
    private static final Logger logger = Logger.getLogger(TeamOrPersonField.class);
62

    
63
    private static final String PRIMARY_STYLE = "v-team-or-person-field";
64

    
65
    private CssLayout root = new CssLayout();
66
    private CssLayout toolBar= new CssLayout();
67
    private CssLayout compositeWrapper = new CssLayout();
68

    
69
    private ReloadableLazyComboBox<TeamOrPersonBase> teamOrPersonSelect = new ReloadableLazyComboBox<TeamOrPersonBase>(TeamOrPersonBase.class);
70

    
71
    private Button removeButton = ButtonFactory.REMOVE_ALL_ITEMS.createButton();
72
    private Button personButton = new Button(FontAwesome.USER);
73
    private Button teamButton = new Button(FontAwesome.USERS);
74

    
75
    // Fields for case when value is a Person
76
    private PersonField personField = new PersonField();
77

    
78
    // Fields for case when value is a Team
79
    private SwitchableTextField titleField = new SwitchableTextField("Team (bibliographic)");
80
    private SwitchableTextField nomenclaturalTitleCacheField = new SwitchableTextField("Team (nomenclatural)");
81
    private ToManyRelatedEntitiesListSelect<Person, PersonField> personsListEditor = new ToManyRelatedEntitiesListSelect<Person, PersonField>(Person.class, PersonField.class, "Team members");
82

    
83
    private BeanFieldGroup<Team> fieldGroup  = new BeanFieldGroup<>(Team.class);
84

    
85
    private TeamOrPersonBaseCaptionGenerator.CacheType cacheType;
86

    
87
    protected List<Component> editorComponents = Arrays.asList(removeButton, personButton, teamButton, teamOrPersonSelect);
88

    
89
    public TeamOrPersonField(String caption, TeamOrPersonBaseCaptionGenerator.CacheType cacheType){
90

    
91
        setCaption(caption);
92

    
93
        this.cacheType = cacheType;
94
        teamOrPersonSelect.setCaptionGenerator(new TeamOrPersonBaseCaptionGenerator<TeamOrPersonBase>(cacheType));
95

    
96

    
97
        addStyledComponent(teamOrPersonSelect);
98
        addStyledComponent(personField);
99
        addStyledComponent(titleField);
100
        addStyledComponent(nomenclaturalTitleCacheField);
101
        addStyledComponent(personsListEditor);
102
        addStyledComponents(removeButton, personButton, teamButton);
103

    
104

    
105
        addSizedComponent(root);
106
        addSizedComponent(compositeWrapper);
107
        addSizedComponent(personField);
108
        addSizedComponent(titleField);
109
        addSizedComponent(nomenclaturalTitleCacheField);
110
        addSizedComponent(personsListEditor);
111

    
112
        setConverter(new CdmBaseDeproxyConverter<TeamOrPersonBase<?>>());
113

    
114
        updateToolBarButtonStates();
115
    }
116

    
117
    /**
118
     * {@inheritDoc}
119
     */
120
    @Override
121
    protected Component initContent() {
122

    
123
        teamOrPersonSelect.addValueChangeListener(e -> {
124
            setValue(teamOrPersonSelect.getValue(), false, true);
125
        });
126
        teamOrPersonSelect.setWidthUndefined();
127

    
128
        removeButton.addClickListener(e -> {
129
            teamOrPersonSelect.clear();
130
            setValue(null, false, true);
131
        });
132
        removeButton.setDescription("Remove");
133

    
134
        personButton.addClickListener(e -> {
135
            setValue(Person.NewInstance(), false, true);
136
            resetReadOnlyComponents();
137

    
138
        });
139
        personButton.setDescription("Add person");
140
        teamButton.addClickListener(e -> {
141
            setValue(Team.NewInstance(), false, true);
142
            resetReadOnlyComponents();
143
        });
144
        teamButton.setDescription("Add team");
145

    
146
        toolBar.setStyleName(ValoTheme.LAYOUT_COMPONENT_GROUP + " toolbar");
147
        toolBar.addComponents(teamOrPersonSelect,  removeButton, personButton, teamButton);
148

    
149
        compositeWrapper.setStyleName("margin-wrapper");
150
        compositeWrapper.addComponent(toolBar);
151

    
152
        root.setPrimaryStyleName(PRIMARY_STYLE);
153
        root.addComponent(compositeWrapper);
154
        return root;
155
    }
156

    
157
    /**
158
     * {@inheritDoc}
159
     */
160
    @Override
161
    public Class getType() {
162
        return TeamOrPersonBase.class;
163
    }
164

    
165
    private void updateToolBarButtonStates(){
166

    
167
        TeamOrPersonBase<?> val = getInternalValue();
168
        boolean userCanCreate = UserHelperAccess.userHelper().userHasPermission(Person.class, "CREATE");
169

    
170
        teamOrPersonSelect.setVisible(val == null);
171
        removeButton.setVisible(!isReadOnly() && val != null);
172
        personButton.setEnabled(!isReadOnly() && userCanCreate && val == null);
173
        teamButton.setEnabled(!isReadOnly() && userCanCreate && val == null);
174
    }
175

    
176
    /**
177
     * {@inheritDoc}
178
     */
179
    @Override
180
    protected void setInternalValue(TeamOrPersonBase<?> newValue) {
181

    
182
        TeamOrPersonBase<?> oldValue = getValue();
183
        super.setInternalValue(newValue);
184

    
185
        newValue = HibernateProxyHelper.deproxy(newValue);
186

    
187
        compositeWrapper.removeAllComponents();
188
        compositeWrapper.addComponent(toolBar);
189

    
190
        if(newValue != null) {
191
            if(Person.class.isAssignableFrom(newValue.getClass())){
192
                // value is a Person
193
                compositeWrapper.addComponent(personField);
194

    
195
                personField.setValue((Person) newValue);
196
                personField.registerParentFieldGroup(fieldGroup);
197

    
198
            }
199
            else if(Team.class.isAssignableFrom(newValue.getClass())){
200
                // otherwise it a Team
201
                compositeWrapper.addComponents(titleField, nomenclaturalTitleCacheField, personsListEditor);
202
                titleField.bindTo(fieldGroup, "titleCache", "protectedTitleCache");
203
                nomenclaturalTitleCacheField.bindTo(fieldGroup, "nomenclaturalTitleCache", "protectedNomenclaturalTitleCache");
204
                fieldGroup.setItemDataSource(new BeanItem<Team>((Team)newValue));
205
                fieldGroup.bind(personsListEditor, "teamMembers");
206
                personsListEditor.registerParentFieldGroup(fieldGroup);
207
            } else {
208
                UserError usererror = new UserError("TeamOrPersonField Error: Unsupported value type: " + newValue.getClass().getName());
209
                setComponentError(usererror);
210
                logger.error(usererror.getMessage());
211
            }
212
        } else {
213
            // new value is null
214
            if(oldValue != null){
215
                // value is null --> clean up all nested fields from old value
216
                // allow replacing old content in the editor by null
217
                setReadOnlyComponents(false);
218
                if(Person.class.isAssignableFrom(oldValue.getClass())){
219
                    personField.unregisterParentFieldGroup(fieldGroup);
220
                    personField.setReadOnly(false);
221
                    personField.setValue((Person) null);
222
                } else if(Team.class.isAssignableFrom(oldValue.getClass())){
223
                    titleField.unbindFrom(fieldGroup);
224
                    nomenclaturalTitleCacheField.unbindFrom(fieldGroup);
225
                    fieldGroup.unbind(personsListEditor);
226
                    fieldGroup.setItemDataSource((Team)null);
227
                    personsListEditor.registerParentFieldGroup(null);
228
                    personsListEditor.setReadOnly(false);
229
                    personsListEditor.setValue(null);
230
                    personsListEditor.registerParentFieldGroup(null);
231
                } else {
232
                    UserError usererror = new UserError("TeamOrPersonField Error: Unsupported value type in oldValue: " + oldValue.getClass().getName());
233
                    setComponentError(usererror);
234
                    logger.error(usererror.getMessage());
235
                }
236
            }
237
        }
238
        adaptToUserPermissions(newValue);
239
        updateToolBarButtonStates();
240
    }
241

    
242

    
243
    private void adaptToUserPermissions(TeamOrPersonBase teamOrPerson) {
244

    
245
        UserHelper userHelper = UserHelperAccess.userHelper();
246
        boolean canEdit = teamOrPerson == null || !teamOrPerson.isPersited() || userHelper.userHasPermission(teamOrPerson, CRUD.UPDATE);
247
        if(!canEdit){
248
            setReadOnlyComponents(true);
249
            fieldGroup.setReadOnly(true); // really needed?
250
        }
251
    }
252

    
253

    
254
    /**
255
     * {@inheritDoc}
256
     */
257
    @Override
258
    protected void addDefaultStyles() {
259
        // no default styles here
260
    }
261

    
262
    /**
263
     * {@inheritDoc}
264
     */
265
    @Override
266
    public Optional<FieldGroup> getFieldGroup() {
267
        return Optional.of(fieldGroup);
268
    }
269

    
270
    public Component[] getCachFields(){
271
        return new Component[]{titleField, nomenclaturalTitleCacheField};
272
    }
273

    
274
    /**
275
     * @return the teamOrPersonSelect
276
     */
277
    public LazyComboBox<TeamOrPersonBase> getTeamOrPersonSelect() {
278
        return teamOrPersonSelect;
279
    }
280

    
281
    /**
282
     * {@inheritDoc}
283
     */
284
    @SuppressWarnings("unchecked")
285
    @Override
286
    public void commit() throws SourceException, InvalidValueException {
287

    
288
        //need to commit the subfields propagation through the fielGroups is not enough
289
        personField.commit();
290
        personsListEditor.commit();
291
        if(!getState(false).readOnly && getPropertyDataSource().isReadOnly()){
292
            // the TeamOrPersonBase Editor (remove, addPerson, addTeam) is not readonly
293
            // thus removing the TeamOrPerson is allowed. In case the datasource is readonly
294
            // due to missing user grants for the TeamOrPerson it must be set to readWrite to
295
            // make it possible to change the property of the parent
296
            getPropertyDataSource().setReadOnly(false);
297
        }
298

    
299
        super.commit();
300

    
301
        if(hasNullContent()){
302
            getPropertyDataSource().setValue(null);
303
            setValue(null);
304
        }
305

    
306
        TeamOrPersonBase<?> bean = getValue();
307
        boolean isTeam = bean != null && bean instanceof Team;
308
        if(isTeam){
309
            boolean isUnsaved = bean.getId() == 0;
310
            if(isUnsaved){
311
                UserHelperAccess.userHelper().createAuthorityForCurrentUser(bean, EnumSet.of(CRUD.UPDATE, CRUD.DELETE), null);
312
            }
313
        }
314
    }
315

    
316
    /**
317
     * {@inheritDoc}
318
     */
319
    @Override
320
    protected List<Field> nullValueCheckIgnoreFields() {
321

    
322
        List<Field> ignoreFields =  super.nullValueCheckIgnoreFields();
323
        ignoreFields.add(personField);
324
        ignoreFields.add(nomenclaturalTitleCacheField.getUnlockSwitch());
325
        if(nomenclaturalTitleCacheField.getUnlockSwitch().getValue().booleanValue() == false){
326
            ignoreFields.add(nomenclaturalTitleCacheField.getTextField());
327
        }
328
        ignoreFields.add(titleField.getUnlockSwitch());
329
        if(titleField.getUnlockSwitch().getValue().booleanValue() == false){
330
            ignoreFields.add(titleField.getTextField());
331
        }
332
        return ignoreFields;
333
    }
334

    
335
    /**
336
     * {@inheritDoc}
337
     */
338
    @Override
339
    public boolean hasNullContent() {
340

    
341
        TeamOrPersonBase<?> bean = getValue();
342
        if(bean == null) {
343
            return true;
344
        }
345
        if(bean instanceof Team){
346
            // --- Team
347
            return super.hasNullContent();
348
        } else {
349
            // --- Person
350
            return personField.hasNullContent();
351
        }
352
    }
353

    
354
    public void setFilterableTeamPagingProvider(CdmFilterablePagingProvider<AgentBase, TeamOrPersonBase> pagingProvider, CachingPresenter cachingPresenter){
355
        teamOrPersonSelect.loadFrom(pagingProvider, pagingProvider, pagingProvider.getPageSize());
356
        // NOTE:
357
        //   it is important to add the ToOneRelatedEntityReloader to the TeamOrPersonField directly
358
        //   since the value of the select will be immediately passed to the TeamOrPersonField
359
        ToOneRelatedEntityReloader<TeamOrPersonBase<?>> teamOrPersonReloader = new ToOneRelatedEntityReloader<TeamOrPersonBase<?>>(this, cachingPresenter);
360
        this.addValueChangeListener(teamOrPersonReloader);
361
    }
362

    
363
    public void setFilterablePersonPagingProvider(CdmFilterablePagingProvider<AgentBase, Person> pagingProvider, CachingPresenter cachingPresenter){
364

    
365
        personsListEditor.setEntityFieldInstantiator(new EntityFieldInstantiator<PersonField>() {
366

    
367
            @Override
368
            public PersonField createNewInstance() {
369
                PersonField f = new PersonField();
370
                f.setAllowNewEmptyEntity(true); // otherwise new entities can not be added to the personsListEditor
371
                f.getPersonSelect().loadFrom(pagingProvider, pagingProvider, pagingProvider.getPageSize());
372
                f.getPersonSelect().setCaptionGenerator(new TeamOrPersonBaseCaptionGenerator<Person>(cacheType));
373
                // NOTE:
374
                //   it is important to add the ToOneRelatedEntityReloader to the PersonField directly
375
                //   since the value of the select will be immediately passed to the PersonField:
376
                f.addValueChangeListener(new ToOneRelatedEntityReloader<Person>(f, cachingPresenter));
377
                return f;
378
            }
379
        });
380
    }
381

    
382
    @Override
383
    public void setValue(TeamOrPersonBase<?> newFieldValue) {
384
        // ignore readonly states of the datasource
385
        setValue(newFieldValue, false, !hasSharedStateReadOnly());
386
    }
387

    
388
    protected boolean hasSharedStateReadOnly(){
389
        AbstractFieldState sharedState = getState(false);
390
        return sharedState.readOnly;
391
    }
392

    
393

    
394
    /**
395
     * {@inheritDoc}
396
     */
397
    @Override
398
    public void setReadOnly(boolean readOnly) {
399
//        super.setReadOnly(readOnly); // moved into setEditorReadOnly()
400
        setReadOnlyComponents(readOnly);
401
    }
402

    
403
    public void setEditorReadOnly(boolean readOnly) {
404
        super.setReadOnly(readOnly);
405
        for(Component c : editorComponents){
406
            applyReadOnlyState(c, readOnly);
407
        }
408

    
409
    }
410

    
411
    /**
412
     * Reset the readonly state of nested components to <code>false</code>.
413
     */
414
    protected void resetReadOnlyComponents() {
415
        if(!isReadOnly()){
416
            setReadOnlyComponents(false);
417
        }
418
    }
419

    
420
    /**
421
     * Set the nested components (team or person fields) to read only but
422
     * keep the state of the <code>TeamOrPersonField</code> untouched so
423
     * that the <code>teamOrPersonSelect</code>, <code>removeButton</code>,
424
     * <code>personButton</code> and <code>teamButton</code> stay operational.
425
     *
426
     * @param readOnly
427
     */
428
    protected void setReadOnlyComponents(boolean readOnly) {
429

    
430
        // setDeepReadOnly(readOnly, getContent(), editorComponents);
431
        // editorComponents.forEach(c -> c.setEnabled(!readOnly));
432
//        personField.setReadOnly(readOnly);
433
//        personsListEditor.setReadOnly(readOnly);
434

    
435
        updateCaptionReadonlyNotice(readOnly);
436
    }
437

    
438

    
439

    
440
}
(6-6/8)