ref #7600 adding usage example to EntityCollectionSetterAdapter doc
[cdmlib.git] / cdmlib-model / src / main / java / eu / etaxonomy / cdm / model / EntityCollectionSetterAdapter.java
1 /**
2 * Copyright (C) 2018 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.model;
10
11 import java.lang.reflect.InvocationTargetException;
12 import java.lang.reflect.Method;
13 import java.util.ArrayList;
14 import java.util.Collection;
15 import java.util.List;
16
17 import org.apache.commons.lang3.StringUtils;
18
19 import eu.etaxonomy.cdm.model.common.CdmBase;
20
21 /**
22 * In many cases the add*() and remove*() methods of entity classes contain important business
23 * logic which would be short-circuited if the set*() method would be public. This adapter allows
24 * providing setter methods to make the bean property writable.
25 * <p>
26 * The {{@link #setCollection(CdmBase, Collection)} method uses the add*() and remove*() methods
27 * in order to update the collection field of the bean.
28 * <p>
29 * Usage example:
30 * <pre>
31 * &#64;Transient
32 * &#64;Transient
33 * private EntityCollectionSetterAdapter<Team, Person> teamMembersSetterAdapter = new EntityCollectionSetterAdapter<Team, Person>(Team.class, Person.class, "teamMembers");
34 *
35 * public void setTeamMembers(List<Person> teamMembers) throws SetterAdapterException {
36 * teamMembersSetterAdapter.setCollection(this, teamMembers);
37 * }
38 </pre>
39 *
40 * see https://dev.e-taxonomy.eu/redmine/issues/7600
41 *
42 * @author a.kohlbecker
43 * @since Nov 15, 2018
44 *
45 */
46 public class EntityCollectionSetterAdapter<CDM extends CdmBase, T extends CdmBase> {
47
48 private Class<T> propertyItemType;
49 private Method getMethod;
50 private Method addMethod;
51 private Method removeMethod;
52 private Exception getMethodException;
53 private Exception addMethodException;
54 private Exception removeMethodException;
55
56 public EntityCollectionSetterAdapter(Class<CDM> beanClass, Class<T> propertyItemType, String propertyName){
57 this(beanClass,
58 propertyItemType,
59 propertyName,
60 "add" + StringUtils.capitalize(propertyName.substring(0, propertyName.length() - 1)),
61 "remove" + StringUtils.capitalize(propertyName.substring(0, propertyName.length() - 1))
62 );
63 }
64
65 public EntityCollectionSetterAdapter(Class<CDM> beanClass, Class<T> propertyItemType, String propertyName, String addMethodName, String removMethodName){
66
67 this.propertyItemType = propertyItemType;
68 try {
69 getMethod = beanClass.getDeclaredMethod("get" + StringUtils.capitalize(propertyName));
70 } catch (NoSuchMethodException | SecurityException e) {
71 getMethodException = e;
72 }
73 try {
74 addMethod = beanClass.getDeclaredMethod(addMethodName, propertyItemType);
75 } catch (NoSuchMethodException | SecurityException e) {
76 addMethodException = e;
77 }
78 try {
79 removeMethod = beanClass.getDeclaredMethod(removMethodName, propertyItemType);
80 } catch (NoSuchMethodException | SecurityException e) {
81 removeMethodException = e;
82 }
83 }
84
85 public void setCollection(CDM bean, Collection<T> items) throws SetterAdapterException {
86
87 if(getMethodException != null){
88 throw new SetterAdapterException("No getter method due to previous exception.", getMethodException);
89 }
90 if(addMethodException != null){
91 throw new SetterAdapterException("No add method due to previous exception.", addMethodException);
92 }
93 if(removeMethodException != null){
94 throw new SetterAdapterException("No remove method due to previous exception.", removeMethodException);
95 }
96
97 try{
98 Collection<T> getterCollection = (Collection<T>) getMethod.invoke(bean);
99 List<T> currentItems = new ArrayList<>(getterCollection);
100 List<T> itemsSeen = new ArrayList<>();
101 for(T a : items){
102 if(a == null){
103 continue;
104 }
105 if(!currentItems.contains(a)){
106 addMethod.invoke(bean, a);
107 }
108 itemsSeen.add(a);
109 }
110 for(T a : currentItems){
111 if(!itemsSeen.contains(a)){
112 removeMethod.invoke(bean, a);
113 }
114 }
115 } catch(ClassCastException e){
116 throw new SetterAdapterException("getter return type (" + getMethod.getReturnType() + ") incompatible with expected property type " + propertyItemType, e);
117 } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
118 throw new SetterAdapterException("error invoking method", e);
119 }
120 }
121
122 public static class SetterAdapterException extends Exception {
123
124 private static final long serialVersionUID = 1L;
125
126 /**
127 * @param message
128 * @param cause
129 */
130 public SetterAdapterException(String message, Throwable cause) {
131 super(message, cause);
132 }
133
134
135
136 }
137
138 }