ref #6529 first implementation of WorkingSet taxon filter
[cdmlib.git] / cdmlib-model / src / main / java / eu / etaxonomy / cdm / model / description / WorkingSet.java
1 /**
2 * Copyright (C) 2007 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
10 package eu.etaxonomy.cdm.model.description;
11
12 import java.util.HashSet;
13 import java.util.Iterator;
14 import java.util.List;
15 import java.util.Set;
16
17 import javax.persistence.Entity;
18 import javax.persistence.FetchType;
19 import javax.persistence.JoinTable;
20 import javax.persistence.ManyToMany;
21 import javax.persistence.ManyToOne;
22 import javax.persistence.OneToMany;
23 import javax.persistence.Transient;
24 import javax.validation.constraints.NotNull;
25 import javax.xml.bind.annotation.XmlAccessType;
26 import javax.xml.bind.annotation.XmlAccessorType;
27 import javax.xml.bind.annotation.XmlElement;
28 import javax.xml.bind.annotation.XmlElementWrapper;
29 import javax.xml.bind.annotation.XmlIDREF;
30 import javax.xml.bind.annotation.XmlRootElement;
31 import javax.xml.bind.annotation.XmlSchemaType;
32 import javax.xml.bind.annotation.XmlType;
33
34 import org.apache.log4j.Logger;
35 import org.hibernate.annotations.Cascade;
36 import org.hibernate.annotations.CascadeType;
37 import org.hibernate.envers.Audited;
38
39 import eu.etaxonomy.cdm.model.common.AnnotatableEntity;
40 import eu.etaxonomy.cdm.model.common.Language;
41 import eu.etaxonomy.cdm.model.common.Representation;
42 import eu.etaxonomy.cdm.model.location.NamedArea;
43 import eu.etaxonomy.cdm.model.name.Rank;
44 import eu.etaxonomy.cdm.model.taxon.TaxonNode;
45
46 /**
47 *
48 * The working set class allows the demarcation of a set of descriptions
49 * associated with representations and a set of features and their
50 * dependencies.
51 *
52 * @author h.fradin
53 * @created 12.08.2009
54 */
55
56 @XmlAccessorType(XmlAccessType.FIELD)
57 @XmlType(name = "WorkingSet", propOrder = {
58 "representations",
59 "descriptiveSystem",
60 "descriptions"
61 })
62 @XmlRootElement(name = "WorkingSet")
63 @Entity
64 @Audited
65
66 public class WorkingSet extends AnnotatableEntity {
67 private static final long serialVersionUID = 3256448866757415686L;
68 private static final Logger logger = Logger.getLogger(WorkingSet.class);
69
70 @XmlElementWrapper(name = "Representations")
71 @XmlElement(name = "Representation")
72 @OneToMany(fetch=FetchType.EAGER)
73 @Cascade( { CascadeType.SAVE_UPDATE, CascadeType.MERGE, CascadeType.DELETE })
74 private Set<Representation> representations = new HashSet<>();
75
76 @XmlElement(name = "DescriptiveSystem")
77 @XmlIDREF
78 @XmlSchemaType(name = "IDREF")
79 @ManyToOne(fetch = FetchType.LAZY)
80 @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
81 private FeatureTree descriptiveSystem;
82
83 @XmlElementWrapper(name = "Descriptions")
84 @XmlElement(name = "Description")
85 @XmlIDREF
86 @XmlSchemaType(name = "IDREF")
87 @ManyToMany(fetch = FetchType.LAZY)
88 @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
89 @NotNull
90 private Set<DescriptionBase> descriptions = new HashSet<>();
91
92 @XmlElementWrapper(name = "SubtreeTaxonFilter")
93 @XmlElement(name = "Subtree")
94 @XmlIDREF
95 @XmlSchemaType(name = "IDREF")
96 @ManyToMany(fetch = FetchType.LAZY)
97 @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
98 @NotNull
99 //a positive filter that defines that all taxa in the subtree belong to
100 //the dataset. If the filter is NOT set, taxa need to be explicitly defined
101 //via the descriptions set. If the filter is set all taxa not having
102 //a description in descriptions yet are considered to have an empty description
103 //TODO what, if a taxon is removed from the subtree but a description exists in
104 //descriptions
105 private Set<TaxonNode> taxonSubtreeFilter = new HashSet<>();
106
107 @XmlElementWrapper(name = "GeoFilter")
108 @XmlElement(name = "FilteredArea")
109 @XmlIDREF
110 @XmlSchemaType(name = "IDREF")
111 @ManyToMany(fetch = FetchType.LAZY)
112 @Cascade({CascadeType.SAVE_UPDATE,CascadeType.MERGE})
113 @JoinTable(name="WorkingSet_NamedArea")
114 @NotNull
115 private Set<NamedArea> geoFilter = new HashSet<>();
116
117 @XmlElement(name = "minRank")
118 @XmlIDREF
119 @XmlSchemaType(name = "IDREF")
120 @ManyToOne(fetch = FetchType.LAZY)
121 private Rank minRank;
122
123 @XmlElement(name = "maxRank")
124 @XmlIDREF
125 @XmlSchemaType(name = "IDREF")
126 @ManyToOne(fetch = FetchType.LAZY)
127 private Rank maxRank;
128
129 // ******************* FACTORY *********************************************/
130
131 public static WorkingSet NewInstance(){
132 return new WorkingSet();
133 }
134
135
136 // *******************CONSTRUCTOR **********************************/
137 /**
138 * Class constructor: creates a new empty working set instance.
139 */
140 protected WorkingSet() {
141 super();
142 }
143
144 // ******************** GETTER / SETTER ************************/
145
146
147 public Set<TaxonNode> getTaxonSubtreeFilter() {
148 return taxonSubtreeFilter;
149 }
150
151 //make public if needed
152 private void setTaxonSubtreeFilter(Set<TaxonNode> taxonSubtreeFilter) {
153 this.taxonSubtreeFilter = taxonSubtreeFilter;
154 }
155
156 public void addTaxonSubtree(TaxonNode subtree) {
157 this.taxonSubtreeFilter.add(subtree);
158 }
159
160 public void removeTaxonSubtree(TaxonNode subtree) {
161 this.taxonSubtreeFilter.remove(subtree);
162 }
163
164 //geo filter
165 public Set<NamedArea> getGeoFilter() {
166 return geoFilter;
167 }
168 public void setGeoFilter(Set<NamedArea> geoFilter) {
169 this.geoFilter = geoFilter;
170 }
171 public void addGeoFilterArea(NamedArea area){
172 this.geoFilter.add(area);
173 }
174 public boolean removeGeoFilterArea(NamedArea area) {
175 return this.geoFilter.remove(area);
176 }
177
178 //min rank
179 public Rank getMinRank() {
180 return minRank;
181 }
182 public void setMinRank(Rank minRank) {
183 this.minRank = minRank;
184 }
185
186 //max rank
187 public Rank getMaxRank() {
188 return maxRank;
189 }
190 public void setMaxRank(Rank maxRank) {
191 this.maxRank = maxRank;
192 }
193
194 //representations
195 public Set<Representation> getRepresentations() {
196 return this.representations;
197 }
198 public void addRepresentation(Representation representation) {
199 this.representations.add(representation);
200 }
201 public void removeRepresentation(Representation representation) {
202 this.representations.remove(representation);
203 }
204
205 public Representation getRepresentation(Language lang) {
206 for (Representation repr : representations){
207 Language reprLanguage = repr.getLanguage();
208 if (reprLanguage != null && reprLanguage.equals(lang)){
209 return repr;
210 }
211 }
212 return null;
213 }
214
215 /**
216 * @see #getPreferredRepresentation(Language)
217 * @param language
218 * @return
219 */
220 public Representation getPreferredRepresentation(Language language) {
221 Representation repr = getRepresentation(language);
222 if(repr == null){
223 repr = getRepresentation(Language.DEFAULT());
224 }
225 if(repr == null){
226 repr = getRepresentations().iterator().next();
227 }
228 return repr;
229 }
230
231 /**
232 * Returns the Representation in the preferred language. Preferred languages
233 * are specified by the parameter languages, which receives a list of
234 * Language instances in the order of preference. If no representation in
235 * any preferred languages is found the method falls back to return the
236 * Representation in Language.DEFAULT() and if necessary further falls back
237 * to return the first element found if any.
238 *
239 * TODO think about this fall-back strategy &
240 * see also {@link TextData#getPreferredLanguageString(List)}
241 *
242 * @param languages
243 * @return
244 */
245 public Representation getPreferredRepresentation(List<Language> languages) {
246 Representation repr = null;
247 if(languages != null){
248 for(Language language : languages) {
249 repr = getRepresentation(language);
250 if(repr != null){
251 return repr;
252 }
253 }
254 }
255 if(repr == null){
256 repr = getRepresentation(Language.DEFAULT());
257 }
258 if(repr == null){
259 Iterator<Representation> it = getRepresentations().iterator();
260 if(it.hasNext()){
261 repr = getRepresentations().iterator().next();
262 }
263 }
264 return repr;
265 }
266
267 @Transient
268 public String getLabel() {
269 if(getLabel(Language.DEFAULT())!=null){
270 Representation repr = getRepresentation(Language.DEFAULT());
271 return (repr == null)? null :repr.getLabel();
272 }else{
273 for (Representation r : representations){
274 return r.getLabel();
275 }
276 }
277 return super.getUuid().toString();
278 }
279
280 public String getLabel(Language lang) {
281 Representation repr = this.getRepresentation(lang);
282 return (repr == null) ? null : repr.getLabel();
283 }
284
285 public void setLabel(String label){
286 Language lang = Language.DEFAULT();
287 setLabel(label, lang);
288 }
289
290 public void setLabel(String label, Language language){
291 if (language != null){
292 Representation repr = getRepresentation(language);
293 if (repr != null){
294 repr.setLabel(label);
295 }else{
296 repr = Representation.NewInstance(null, label, null, language);
297 }
298 this.addRepresentation(repr);
299 }
300 }
301
302 public FeatureTree getDescriptiveSystem() {
303 return descriptiveSystem;
304 }
305 public void setDescriptiveSystem(FeatureTree descriptiveSystem) {
306 this.descriptiveSystem = descriptiveSystem;
307 }
308
309 /**
310 * Returns the {@link DescriptionBase descriptions} of
311 * <i>this</i> working set.
312 *
313 * @see #addDescription(DescriptionBase)
314 * @see #removeDescription(DescriptionBase)
315 */
316 public Set<DescriptionBase> getDescriptions() {
317 return descriptions;
318 }
319
320 /**
321 * Adds an existing {@link DescriptionBase description} to the set of
322 * {@link #getDescriptions() descriptions} of <i>this</i>
323 * working set.
324 *
325 * @param description the description to be added to <i>this</i> working set
326 * @see #getDescriptions()
327 * @see WorkingSet#addDescription(DescriptionBase)
328 */
329 public boolean addDescription(DescriptionBase description) {
330 boolean result = this.descriptions.add(description);
331 if (! description.getWorkingSets().contains(this)){
332 description.addWorkingSet(this);
333 }
334 return result;
335 }
336
337 /**
338 * Removes one element from the set of {@link #getDescriptions() descriptions} involved
339 * in <i>this</i> working set.<BR>
340 *
341 * @param description the description which should be removed
342 * @see #getDescriptions()
343 * @see #addDescription(DescriptionBase)
344 * @see WorkingSet#removeDescription(DescriptionBase)
345 */
346 public boolean removeDescription(DescriptionBase description) {
347 boolean result = this.descriptions.remove(description);
348 if (description.getWorkingSets().contains(this)){
349 description.removeWorkingSet(this);
350 }
351 return result;
352 }
353
354 //*********************** CLONE ********************************************************/
355
356 /**
357 * Clones <i>this</i> WorkingSet. This is a shortcut that enables to create
358 * a new instance that differs only slightly from <i>this</i> WorkingSet by
359 * modifying only some of the attributes.
360 * The descriptions and the descriptive system are the same, the representations
361 * are cloned.
362 *
363 * @see eu.etaxonomy.cdm.model.common.AnnotatableEntity#clone()
364 * @see java.lang.Object#clone()
365 */
366 @Override
367 public Object clone() {
368 WorkingSet result;
369 try {
370 result = (WorkingSet)super.clone();
371
372 //descriptions
373 result.descriptions = new HashSet<>();
374 for (DescriptionBase<?> desc: this.descriptions){
375 result.addDescription(desc);
376 }
377
378 //representations
379 result.representations = new HashSet<>();
380 for (Representation rep : this.representations){
381 result.addRepresentation((Representation)rep.clone());
382 }
383
384 //subtree filter
385 result.taxonSubtreeFilter = new HashSet<>();
386 for (TaxonNode subtree : this.taxonSubtreeFilter){
387 result.addTaxonSubtree(subtree);
388 }
389
390 //geo filter
391 result.geoFilter = new HashSet<>();
392 for (NamedArea area : this.geoFilter){
393 result.addGeoFilterArea(area);
394 }
395
396 return result;
397 }catch (CloneNotSupportedException e) {
398 logger.warn("Object does not implement cloneable");
399 e.printStackTrace();
400 return null;
401 }
402 }
403
404 }