Project

General

Profile

Download (16.1 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

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

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

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

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

    
58
    private static final long serialVersionUID = 660806402243118112L;
59

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

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

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

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

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

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

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

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

    
84
    private TeamOrPersonBaseCaptionGenerator.CacheType cacheType;
85

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

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

    
90
        setCaption(caption);
91

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

    
95

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

    
103

    
104
        addSizedComponent(root);
105
        addSizedComponent(compositeWrapper);
106
        addSizedComponent(personField);
107
        addSizedComponent(titleField);
108
        addSizedComponent(nomenclaturalTitleField);
109
        addSizedComponent(personsListEditor);
110

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

    
113
        updateToolBarButtonStates();
114
    }
115

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

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

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

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

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

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

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

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

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

    
164
    private void updateToolBarButtonStates(){
165

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

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

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

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

    
184
        newValue = HibernateProxyHelper.deproxy(newValue);
185

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

    
189
        if(newValue != null) {
190

    
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, nomenclaturalTitleField, personsListEditor);
202

    
203
                titleField.bindTo(fieldGroup, "titleCache", "protectedTitleCache");
204
                nomenclaturalTitleField.bindTo(fieldGroup, "nomenclaturalTitle", "protectedNomenclaturalTitleCache");
205
                fieldGroup.setItemDataSource(new BeanItem<Team>((Team)newValue));
206
                boolean readonlyState = personsListEditor.isReadOnly();
207
                fieldGroup.bind(personsListEditor, "teamMembers"); // here personField is set readonly since setTeamMembers does not exist
208
                personsListEditor.setReadOnly(readonlyState); // fixing the readonly state
209

    
210
                personsListEditor.registerParentFieldGroup(fieldGroup);
211

    
212
            } else {
213
                UserError usererror = new UserError("TeamOrPersonField Error: Unsupported value type: " + newValue.getClass().getName());
214
                setComponentError(usererror);
215
                logger.error(usererror.getMessage());
216
            }
217
        } else {
218
            if(oldValue != null){
219
                // value is null --> clean up all nested fields
220
                // allow replacing old content in the editor by null
221
                setReadOnlyComponents(false);
222
                if(Person.class.isAssignableFrom(oldValue.getClass())){
223
                    personField.unregisterParentFieldGroup(fieldGroup);
224
                    personField.setReadOnly(false);
225
                    personField.setValue((Person) null);
226
                } else if(Team.class.isAssignableFrom(oldValue.getClass())){
227
                    titleField.unbindFrom(fieldGroup);
228
                    nomenclaturalTitleField.unbindFrom(fieldGroup);
229
                    fieldGroup.unbind(personsListEditor);
230
                    fieldGroup.setItemDataSource((Team)null);
231
                    personsListEditor.registerParentFieldGroup(null);
232
                    personsListEditor.setReadOnly(false);
233
                    personsListEditor.setValue(null);
234
                    personsListEditor.registerParentFieldGroup(null);
235
                } else {
236
                    UserError usererror = new UserError("TeamOrPersonField Error: Unsupported value type in oldValue: " + oldValue.getClass().getName());
237
                    setComponentError(usererror);
238
                    logger.error(usererror.getMessage());
239
                }
240
            }
241
        }
242
        adaptToUserPermissions(newValue);
243
        updateToolBarButtonStates();
244
    }
245

    
246

    
247
    private void adaptToUserPermissions(TeamOrPersonBase teamOrPerson) {
248

    
249
        UserHelper userHelper = UserHelperAccess.userHelper();
250
        boolean canEdit = teamOrPerson == null || !teamOrPerson.isPersited() || userHelper.userHasPermission(teamOrPerson, CRUD.UPDATE);
251
        if(!canEdit){
252
            setReadOnlyComponents(true);
253
        }
254
    }
255

    
256

    
257
    /**
258
     * {@inheritDoc}
259
     */
260
    @Override
261
    protected void addDefaultStyles() {
262
        // no default styles here
263
    }
264

    
265
    /**
266
     * {@inheritDoc}
267
     */
268
    @Override
269
    public FieldGroup getFieldGroup() {
270
        return fieldGroup;
271
    }
272

    
273
    public Component[] getCachFields(){
274
        return new Component[]{titleField, nomenclaturalTitleField};
275
    }
276

    
277
    /**
278
     * @return the teamOrPersonSelect
279
     */
280
    public LazyComboBox<TeamOrPersonBase> getTeamOrPersonSelect() {
281
        return teamOrPersonSelect;
282
    }
283

    
284
    /**
285
     * {@inheritDoc}
286
     */
287
    @SuppressWarnings("unchecked")
288
    @Override
289
    public void commit() throws SourceException, InvalidValueException {
290

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

    
302
        super.commit();
303

    
304
        if(hasNullContent()){
305
            getPropertyDataSource().setValue(null);
306
            setValue(null);
307
        }
308

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

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

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

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

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

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

    
365
    public void setFilterablePersonPagingProvider(CdmFilterablePagingProvider<AgentBase, Person> pagingProvider, CachingPresenter cachingPresenter){
366

    
367
        personsListEditor.setEntityFieldInstantiator(new EntityFieldInstantiator<PersonField>() {
368

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

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

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

    
395

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

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

    
411
    }
412

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

    
422
    /**
423
     * Set the nested components (team or person fields) to read only but
424
     * keep the state of the <code>TeamOrPersonField</code> untouched so
425
     * that the <code>teamOrPersonSelect</code>, <code>removeButton</code>,
426
     * <code>personButton</code> and <code>teamButton</code> stay operational.
427
     *
428
     * @param readOnly
429
     */
430
    protected void setReadOnlyComponents(boolean readOnly) {
431
        setDeepReadOnly(readOnly, getContent(), editorComponents);
432
        updateCaptionReadonlyNotice(readOnly);
433
    }
434

    
435

    
436

    
437
}
(6-6/8)