Project

General

Profile

Download (12.7 KB) Statistics
| Branch: | Tag: | Revision:
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
package eu.etaxonomy.cdm.model.metadata;
10

    
11
import java.io.Serializable;
12
import java.util.ArrayList;
13
import java.util.Arrays;
14
import java.util.List;
15
import java.util.UUID;
16
import java.util.stream.Collectors;
17

    
18
import javax.persistence.Column;
19
import javax.persistence.Embeddable;
20
import javax.persistence.EmbeddedId;
21
import javax.persistence.Entity;
22
import javax.persistence.Lob;
23

    
24
import org.apache.commons.lang3.StringUtils;
25

    
26
import eu.etaxonomy.cdm.common.CdmUtils;
27

    
28
/**
29
 * This class holds all preference data for a CDM database.
30
 * E.g. one may store what the default nomenclatural code in a database is,
31
 * or which formatter (cache strategy) to use in a certain context.
32
 * <BR><BR>
33
 * The structure represents a triple where the first item
34
 * (subject) defines in which context or for which object the given
35
 * information is valid. The second item (predicate) describes the
36
 * type of information and the third item (value) represents the actual value.
37
 * <BR><BR>
38
 * E.g. given the usecase of formatting terms in a UI.
39
 * The predicate maybe something like "preferredTermFormatting".
40
 * While the subject may define the context, e.g. "/" for the project
41
 * wide default formatting while "/vaadin" may define the formatting
42
 * used in a vaadin context and "/taxeditor" may define the formatting
43
 * in TaxEditor context. Even more specific "/taxeditor/detailsview/dropdown"
44
 * may define the correct formatting in dropdown elements in the detailsview
45
 * while "/taxeditor/termeditor" may define the correct formatting in the taxeditors
46
 * term editor.
47
 * <BR><BR>
48
 * Another more data centric example from checklists is the preference which distribution status terms
49
 * should be available for which area. The predicate used here is
50
 * {@link PreferencePredicate#AvailableDistributionStatus} for both preferences. The preference
51
 * which defines the default list of distribution status terms uses "/" as subject and
52
 * a list of UUIDs as value.
53
 * <BR><BR>
54
 * The preference defining the specific distribution status terms only available for the
55
 * top level area, e.g. "Euro+Med" uses the subject "/NamedArea[111bdf38-7a32-440a-9808-8af1c9e54b51]/"
56
 * (with 111bd... being the UUID of the top level area) and another UUID list as value.
57
 * <BR><BR>
58
 * Subjects by convention should be defined hierarchical following a slash syntax like
59
 * "/level1/level2/level3". For how to resolve a subject see also
60
 * {@link PreferenceResolver#resolve(List, PrefKey)}.<BR>
61
 * Generally the subject is String based but clients may implement classes or enumerations
62
 * to ease the hierarchical handling of subjects. With {@link PreferenceSubject} there is already
63
 * a very basic implementation to allow to distinguish project wide, vaadin and taxeditor subjects.
64
 * This may be further developed in future.
65
 * <BR><BR>
66
 * If doubt exists on best practice for how to use subjects please contact the author.
67
 * <BR>
68
 * <BR>
69
 *  The set of allowed values and semantics for each combination
70
 *  will be defined by convention  over time and by implementing classes.
71
 *  The only real restrictions we have is the length of the fields and
72
 *  the fact that the first two items (subject, predicate) must be unique
73
 *  key in the given database.
74
 *
75
 *  Size of single fields may be enlarged in future versions.
76
 *
77
 * @author a.mueller
78
 * @since 03.07.2013
79
 */
80
@Entity
81
public final class CdmPreference implements Serializable {
82

    
83
    private static final String STRING_LIST_SEPARATOR = "[,;\\s]";
84

    
85
    private static final int VALUE_LENGTH = 65536; //= CdmBase.CLOB_LENGTH;
86

    
87
    private static final long serialVersionUID = 4307599154287181582L;
88

    
89
    public static final CdmPreference NewInstance(PreferenceSubject subject,
90
            IPreferencePredicate<?> predicate, String value){
91
        return new CdmPreference(subject, predicate, value);
92
    }
93

    
94
    public static final CdmPreference NewInstance(PrefKey key, String value){
95
        return new CdmPreference(key.subject, key.predicate, value);
96
    }
97

    
98
    public static final CdmPreference NewInstance(PreferenceSubject subject, IPreferencePredicate<?> predicate, List<UUID> value){
99
        return new CdmPreference(subject, predicate, uuidListStr(value));
100
    }
101
    public static final CdmPreference NewInstance(PreferenceSubject subject, IPreferencePredicate<?> predicate, UUID ... value){
102
        return new CdmPreference(subject, predicate, uuidListStr(Arrays.asList(value)));
103
    }
104

    
105
    public static final CdmPreference NewInstance(PreferenceSubject subject, IPreferencePredicate<?> predicate, UUID value){
106
        return new CdmPreference(subject, predicate, value.toString());
107
    }
108

    
109
    public static CdmPreference NewDatabaseInstance(IPreferencePredicate<?> predicate, String value) {
110
        return new CdmPreference(PreferenceSubject.NewDatabaseInstance(), predicate, value);
111
    }
112

    
113
    public static CdmPreference NewVaadinInstance(IPreferencePredicate<?> predicate, String value) {
114
        return new CdmPreference(PreferenceSubject.NewVaadinInstance(), predicate, value);
115
    }
116

    
117
    public static CdmPreference NewTaxEditorInstance(IPreferencePredicate<?> predicate, String value) {
118
        return new CdmPreference(PreferenceSubject.NewTaxEditorInstance(), predicate, value);
119
    }
120

    
121
    public static PrefKey NewKey(PreferenceSubject subject, IPreferencePredicate<?> predicate){
122
      return new PrefKey(subject, predicate);
123
    }
124

    
125
//	public static final CdmPreference NewInstance(PreferenceSubjectEnum subject, PreferencePredicate predicate, String value){
126
//		return new CdmPreference(subject, predicate, value);
127
//	}
128
//
129
//	public static PrefKey NewKey(PreferenceSubjectEnum subject, PreferencePredicate predicate){
130
//		return new PrefKey(subject, predicate);
131
//	}
132

    
133

    
134
	@EmbeddedId
135
	private PrefKey key;
136

    
137
	@Column(length=VALUE_LENGTH)
138
	@Lob
139
	private String value;
140

    
141
    //if false, the preference should not be overridden by local preferences,
142
	//if true existing local preferences override database preferences
143
	    //and the database preference only defines the default.
144
    private boolean allowOverride = true;
145

    
146
    @Embeddable
147
    public static class PrefKey implements Serializable{
148

    
149
        private static final long serialVersionUID = 9019957853773606194L;
150

    
151
        //required syntax:  /([A-Za-z]+\[.*\])?
152
        //examples /  ,  /TaxonNode[#t10#44#1456#]  for a taxon node preference
153
        @Column(name="key_subject", length=100) //for now we keep the combined key short as indizes for such keys are very limited in size in some DBMS. Size may be increased later
154
        private String subject;
155

    
156
        @Column(name="key_predicate", length=100) //for now we keep the combined key short as indizes for such keys are very limited in size in some DBMS. Size may be increased later
157
        private String predicate;
158

    
159
        //for hibernate use only
160
        private PrefKey(){}
161

    
162
        private PrefKey(PreferenceSubject subject, IPreferencePredicate<?> predicate){
163
            this(subject.toString(), predicate.getKey());
164
        }
165
//      private PrefKey(PreferenceSubjectEnum subject, PreferencePredicate predicate){
166
//          this(subject.getKey(), predicate.getKey());
167
//      }
168

    
169
        private PrefKey(String subject, String predicate){
170
            if (subject == null) {
171
                throw new IllegalArgumentException("Subject must not be null for preference");
172
            }
173
            if (predicate == null) {
174
                throw new IllegalArgumentException("Predicate must not be null for preference");
175
            }
176
            if (subject.length() > 255) {
177
                throw new IllegalArgumentException("Subject must not be longer then 255 for preference");
178
            }
179
            if (predicate.length() > 255) {
180
                throw new IllegalArgumentException("Predicate must not be longer then 255 for preference");
181
            }
182
            if (!(subject.matches(PreferenceSubject.ROOT + "(([A-Za-z]+(\\[.*\\])?|"+PreferenceSubject.VAADIN+")"+PreferenceSubject.SEP+")*")
183
                    || subject.matches(PreferenceSubject.ROOT + "(([A-Za-z]+(\\[.*\\])?|"+PreferenceSubject.TAX_EDITOR+")"+PreferenceSubject.SEP+")*")
184
                    )){
185
                throw new IllegalArgumentException("Subject does not follow the required syntax");
186
            }
187

    
188
            this.subject = subject;
189
            this.predicate = predicate;
190
        }
191

    
192
        @Override
193
        public int hashCode() {
194
            final int prime = 31;
195
            int result = 1;
196
            result = prime * result + ((predicate == null) ? 0 : predicate.hashCode());
197
            result = prime * result + ((subject == null) ? 0 : subject.hashCode());
198
            return result;
199
        }
200

    
201
        @Override
202
        public boolean equals(Object obj) {
203
            if (this == obj){
204
                return true;
205
            } else if (obj == null){
206
                return false;
207
            }else if (getClass() != obj.getClass()){
208
                return false;
209
            }else{
210
                PrefKey other = (PrefKey) obj;
211
                return ( predicate.equals(other.predicate) && subject.equals(other.subject));
212
            }
213
        }
214

    
215
        public String getSubject() {
216
            return subject;
217
        }
218

    
219
        public String getPredicate() {
220
            return predicate;
221
        }
222

    
223
        @Override
224
        public String toString() {
225
            return "PrefKey[" +subject + ": " + predicate +"]";
226
        }
227
    }
228

    
229
//****************** CONSTRUCTOR **********************/
230

    
231
	//for hibernate use only
232
	@SuppressWarnings("unused")
233
	private CdmPreference(){}
234

    
235

    
236
	private CdmPreference(PreferenceSubject subject, IPreferencePredicate<?> predicate, String value){
237
		this.key = new PrefKey(subject, predicate);
238
		checkValue(value);
239
		this.value = value;
240
	}
241

    
242

    
243
	/**
244
	 * Constructor.
245
	 *
246
	 * @param subject must not be null and must not be longer then 255 characters.
247
	 * @param predicate must not be null and must not be longer then 255 characters.
248
	 * @param value must not be longer then 1023 characters.
249
	 */
250
	public CdmPreference(String subject, String predicate, String value){
251
		this.key = new PrefKey(subject, predicate);
252
		checkValue(value);
253
		this.value = value;
254

    
255
	}
256

    
257
    private void checkValue(String value) {
258
        //TODO are null values allowed?     assert predicate != null : "value must not be null for preference";
259
        if (value != null && value.length() > VALUE_LENGTH -1 ) {
260
		    throw new IllegalArgumentException(
261
		            String.format("Preference value must not be longer then "+VALUE_LENGTH+" characters for preference. Value = %s", value));
262
		}
263
    }
264

    
265
//************************ GETTER / SETTER ***************************/
266

    
267
	public boolean isDatabasePref(){
268
	    return PreferenceSubject.ROOT.equals(key.subject);
269
	}
270

    
271
	/**
272
	 * @return the subject of the preference as String.
273
	 */
274
	public String getSubjectString() {
275
		return key.subject;
276
	}
277
	/**
278
     * @return the subject of the preference
279
     */
280
    public PreferenceSubject getSubject() {
281
        return PreferenceSubject.fromKey(key);
282
    }
283

    
284
	/**
285
	 * @return the predicate of the preference
286
	 */
287
	public String getPredicate() {
288
		return key.predicate;
289
	}
290

    
291
	/**
292
	 * @return the value of the preference
293
	 */
294
	public String getValue() {
295
		return value;
296
	}
297

    
298
	/**
299
	 * Returns the {@link #getValue() value} as {@link UUID} List.
300
	 * Throws an exception if the value can not be parsed as UUID list.
301
	 * @return
302
	 * @throws IllegalArgumentException
303
	 */
304
	public List<UUID> getValueUuidList() throws IllegalArgumentException {
305

    
306
	    List<UUID> result = new ArrayList<>();
307
	    for (String split : splitStringListValue()){
308
            UUID uuid = UUID.fromString(split.trim());
309
            result.add(uuid);
310
	    }
311

    
312
	    return result;
313
    }
314

    
315
	/**
316
	 * Splits the <code>value</code> into tokens by the separators defined in {@link #STRING_LIST_SEPARATOR}
317
	 *
318
	 * @return
319
	 */
320
    public  List<String> splitStringListValue() {
321
        List<String> tokens;
322
        if (StringUtils.isNotBlank(value)){
323
	        tokens = Arrays.stream(getValue().split(STRING_LIST_SEPARATOR)).filter(t -> !StringUtils.isBlank(t)).collect(Collectors.toList());
324
	    } else {
325
	        tokens = new ArrayList<>();
326
	    }
327
        return tokens;
328
    }
329

    
330
    protected static String uuidListStr(List<UUID> value) {
331
        String valueStr = "";
332
        for (UUID uuid : value){
333
            valueStr = CdmUtils.concat(",",valueStr, uuid.toString());
334
        }
335
        return valueStr;
336
    }
337

    
338
//
339
//  we try to avoid setting of values
340
//  public void setValue(String value) {
341
//      this.value = value;
342
//  }
343

    
344
	public PrefKey getKey() {
345
		return key;
346
	}
347

    
348
    public boolean isAllowOverride() {
349
        return allowOverride;
350
    }
351

    
352
    public void setAllowOverride(boolean allowOverride) {
353
        this.allowOverride = allowOverride;
354
    }
355

    
356
}
(3-3/19)