cleanup
[cdmlib.git] / cdmlib-services / src / main / java / eu / etaxonomy / cdm / api / service / TermServiceImpl.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.api.service;
11
12 import java.net.URI;
13 import java.util.ArrayList;
14 import java.util.Collection;
15 import java.util.Enumeration;
16 import java.util.HashSet;
17 import java.util.List;
18 import java.util.Locale;
19 import java.util.Map;
20 import java.util.Set;
21 import java.util.UUID;
22
23 import org.apache.commons.lang.StringUtils;
24 import org.apache.log4j.Logger;
25 import org.springframework.beans.factory.annotation.Autowired;
26 import org.springframework.beans.factory.annotation.Qualifier;
27 import org.springframework.stereotype.Service;
28 import org.springframework.transaction.annotation.Transactional;
29
30 import eu.etaxonomy.cdm.api.service.UpdateResult.Status;
31 import eu.etaxonomy.cdm.api.service.config.DeleteConfiguratorBase;
32 import eu.etaxonomy.cdm.api.service.config.TermDeletionConfigurator;
33 import eu.etaxonomy.cdm.api.service.exception.DataChangeNoRollbackException;
34 import eu.etaxonomy.cdm.api.service.exception.ReferencedObjectUndeletableException;
35 import eu.etaxonomy.cdm.api.service.pager.Pager;
36 import eu.etaxonomy.cdm.api.service.pager.impl.DefaultPagerImpl;
37 import eu.etaxonomy.cdm.common.monitor.IProgressMonitor;
38 import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
39 import eu.etaxonomy.cdm.model.common.CdmBase;
40 import eu.etaxonomy.cdm.model.common.DefinedTermBase;
41 import eu.etaxonomy.cdm.model.common.Language;
42 import eu.etaxonomy.cdm.model.common.LanguageString;
43 import eu.etaxonomy.cdm.model.common.LanguageStringBase;
44 import eu.etaxonomy.cdm.model.common.OrderedTermBase;
45 import eu.etaxonomy.cdm.model.common.OrderedTermVocabulary;
46 import eu.etaxonomy.cdm.model.common.Representation;
47 import eu.etaxonomy.cdm.model.common.TermType;
48 import eu.etaxonomy.cdm.model.common.TermVocabulary;
49 import eu.etaxonomy.cdm.model.location.NamedArea;
50 import eu.etaxonomy.cdm.model.location.NamedAreaLevel;
51 import eu.etaxonomy.cdm.model.location.NamedAreaType;
52 import eu.etaxonomy.cdm.model.media.Media;
53 import eu.etaxonomy.cdm.persistence.dao.common.IDefinedTermDao;
54 import eu.etaxonomy.cdm.persistence.dao.common.ILanguageStringBaseDao;
55 import eu.etaxonomy.cdm.persistence.dao.common.ILanguageStringDao;
56 import eu.etaxonomy.cdm.persistence.dao.common.IRepresentationDao;
57 import eu.etaxonomy.cdm.persistence.dto.TermDto;
58 import eu.etaxonomy.cdm.persistence.dto.UuidAndTitleCache;
59 import eu.etaxonomy.cdm.persistence.query.OrderHint;
60 import eu.etaxonomy.cdm.strategy.cache.common.IIdentifiableEntityCacheStrategy;
61
62 @Service
63 @Transactional(readOnly = true)
64 public class TermServiceImpl extends IdentifiableServiceBase<DefinedTermBase,IDefinedTermDao> implements ITermService{
65 @SuppressWarnings("unused")
66 private static final Logger logger = Logger.getLogger(TermServiceImpl.class);
67
68 private ILanguageStringDao languageStringDao;
69
70 @Autowired
71 private IVocabularyService vocabularyService;
72
73 @Autowired
74 @Qualifier("langStrBaseDao")
75 private ILanguageStringBaseDao languageStringBaseDao;
76 private IRepresentationDao representationDao;
77
78 @Autowired
79 public void setLanguageStringDao(ILanguageStringDao languageStringDao) {
80 this.languageStringDao = languageStringDao;
81 }
82
83 @Autowired
84 public void setRepresentationDao(IRepresentationDao representationDao) {
85 this.representationDao = representationDao;
86 }
87
88 @Override
89 @Autowired
90 protected void setDao(IDefinedTermDao dao) {
91 this.dao = dao;
92 }
93
94 @Override
95 public <T extends DefinedTermBase> List<T> listByTermType(TermType termType, Integer limit, Integer start,
96 List<OrderHint> orderHints, List<String> propertyPaths) {
97 return dao.listByTermType(termType, limit, start, orderHints, propertyPaths);
98 }
99
100 @Override
101 public DefinedTermBase getByUri(URI uri) {
102 return dao.findByUri(uri);
103 }
104
105 @Override
106 public Language getLanguageByIso(String iso639) {
107 return dao.getLanguageByIso(iso639);
108 }
109
110 @Override
111 public Language getLanguageByLabel(String label) {
112 return Language.getLanguageByLabel(label);
113 }
114
115 @Override
116 public List<Language> getLanguagesByLocale(Enumeration<Locale> locales){
117 return dao.getLanguagesByLocale(locales);
118 }
119
120 @Override
121 public <TERM extends DefinedTermBase> TERM findByIdInVocabulary(String id, UUID vocabularyUuid, Class<TERM> clazz) throws IllegalArgumentException {
122 List<TERM> list = dao.getDefinedTermByIdInVocabulary(id, vocabularyUuid, clazz, null, null);
123 if (list.isEmpty()){
124 return null;
125 }else if (list.size() == 1){
126 return list.get(0);
127 }else{
128 String message = "There is more then 1 (%d) term with the same id in vocabulary. This is forbidden. Check the state of your database.";
129 throw new IllegalStateException(String.format(message, list.size()));
130 }
131 }
132
133
134 @Override
135 public NamedArea getAreaByTdwgAbbreviation(String tdwgAbbreviation) {
136 if (StringUtils.isBlank(tdwgAbbreviation)){ //TDWG areas should always have a label
137 return null;
138 }
139 List<NamedArea> list = dao.getDefinedTermByIdInVocabulary(tdwgAbbreviation, NamedArea.uuidTdwgAreaVocabulary, NamedArea.class, null, null);
140 if (list.isEmpty()){
141 return null;
142 }else if (list.size() == 1){
143 return list.get(0);
144 }else{
145 String message = "There is more then 1 (%d) TDWG area with the same abbreviated label. This is forbidden. Check the state of your database.";
146 throw new IllegalStateException(String.format(message, list.size()));
147 }
148 }
149
150 @Override
151 public <T extends DefinedTermBase> Pager<T> getGeneralizationOf(T definedTerm, Integer pageSize, Integer pageNumber) {
152 long numberOfResults = dao.countGeneralizationOf(definedTerm);
153
154 List<T> results = new ArrayList<>();
155 if(numberOfResults > 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
156 results = dao.getGeneralizationOf(definedTerm, pageSize, pageNumber);
157 }
158
159 return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);
160 }
161
162 @Override
163 public <T extends DefinedTermBase> Pager<T> getIncludes(Collection<T> definedTerms, Integer pageSize, Integer pageNumber, List<String> propertyPaths) {
164 long numberOfResults = dao.countIncludes(definedTerms);
165
166 List<T> results = new ArrayList<>();
167 if(numberOfResults > 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
168 results = dao.getIncludes(definedTerms, pageSize, pageNumber,propertyPaths);
169 }
170
171 return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);
172 }
173
174 @Override
175 public Pager<Media> getMedia(DefinedTermBase definedTerm, Integer pageSize, Integer pageNumber) {
176 long numberOfResults = dao.countMedia(definedTerm);
177
178 List<Media> results = new ArrayList<>();
179 if(numberOfResults > 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
180 results = dao.getMedia(definedTerm, pageSize, pageNumber);
181 }
182
183 return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);
184 }
185
186 @Override
187 public <T extends DefinedTermBase> Pager<T> getPartOf(Set<T> definedTerms,Integer pageSize, Integer pageNumber, List<String> propertyPaths) {
188 long numberOfResults = dao.countPartOf(definedTerms);
189
190 List<T> results = new ArrayList<>();
191 if(numberOfResults > 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
192 results = dao.getPartOf(definedTerms, pageSize, pageNumber, propertyPaths);
193 }
194
195 return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);
196 }
197
198 @Override
199 public Pager<NamedArea> list(NamedAreaLevel level, NamedAreaType type, Integer pageSize, Integer pageNumber,
200 List<OrderHint> orderHints, List<String> propertyPaths) {
201 long numberOfResults = dao.count(level, type);
202
203 List<NamedArea> results = new ArrayList<>();
204 if (numberOfResults > 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
205 results = dao.list(level, type, pageSize, pageNumber, orderHints, propertyPaths);
206 }
207
208 return new DefaultPagerImpl<>(pageNumber, numberOfResults, pageSize, results);
209 }
210
211 @Override
212 public <T extends DefinedTermBase> Pager<T> findByRepresentationText(String label, Class<T> clazz, Integer pageSize, Integer pageNumber) {
213 long numberOfResults = dao.countDefinedTermByRepresentationText(label,clazz);
214
215 List<T> results = new ArrayList<>();
216 if(numberOfResults > 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
217 results = dao.getDefinedTermByRepresentationText(label, clazz, pageSize, pageNumber);
218 }
219
220 return new DefaultPagerImpl<T>(pageNumber, numberOfResults, pageSize, results);
221 }
222
223 @Override
224 public <T extends DefinedTermBase> Pager<T> findByRepresentationAbbreviation(String abbrev, Class<T> clazz, Integer pageSize, Integer pageNumber) {
225 long numberOfResults = dao.countDefinedTermByRepresentationAbbrev(abbrev,clazz);
226
227 List<T> results = new ArrayList<>();
228 if(numberOfResults > 0) { // no point checking again //TODO use AbstractPagerImpl.hasResultsInRange(numberOfResults, pageNumber, pageSize)
229 results = dao.getDefinedTermByRepresentationAbbrev(abbrev, clazz, pageSize, pageNumber);
230 }
231
232 return new DefaultPagerImpl<T>(pageNumber, numberOfResults, pageSize, results);
233 }
234
235 @Override
236 public List<LanguageString> getAllLanguageStrings(int limit, int start) {
237 return languageStringDao.list(limit, start);
238 }
239
240 @Override
241 public List<Representation> getAllRepresentations(int limit, int start) {
242 return representationDao.list(limit,start);
243 }
244
245 @Override
246 public UUID saveLanguageData(LanguageStringBase languageData) {
247 return languageStringBaseDao.save(languageData).getUuid();
248 }
249
250
251 /** @deprecated use {@link #delete(DefinedTermBase, TermDeletionConfigurator)} instead
252 * to allow DeleteResult return type*/
253 @Override
254 @Deprecated
255 public DeleteResult delete(DefinedTermBase term){
256 DeleteResult result = new DeleteResult();
257
258 TermDeletionConfigurator defaultConfig = new TermDeletionConfigurator();
259 result = delete(term, defaultConfig);
260 return result;
261 }
262
263 @Override
264 @Deprecated
265 @Transactional(readOnly = false)
266 public DeleteResult delete(UUID termUuid){
267 DeleteResult result = new DeleteResult();
268
269 TermDeletionConfigurator defaultConfig = new TermDeletionConfigurator();
270 result = delete(dao.load(termUuid), defaultConfig);
271 return result;
272 }
273
274 @Override
275 public DeleteResult delete(DefinedTermBase term, TermDeletionConfigurator config){
276 if (config == null){
277 config = new TermDeletionConfigurator();
278 }
279 Set<DefinedTermBase> termsToSave = new HashSet<DefinedTermBase>();
280
281 DeleteResult result = isDeletable(term.getUuid(), config);
282 if (result.isAbort()) {
283 return result;
284 }
285 //CdmBase.deproxy(dao.merge(term), DefinedTermBase.class);
286
287 try {
288 //generalization of
289 Set<DefinedTermBase> specificTerms = term.getGeneralizationOf();
290 if (specificTerms.size()>0){
291 if (config.isDeleteGeneralizationOfRelations()){
292 DefinedTermBase generalTerm = term.getKindOf();
293 for (DefinedTermBase specificTerm: specificTerms){
294 term.removeGeneralization(specificTerm);
295 if (generalTerm != null){
296 generalTerm.addGeneralizationOf(specificTerm);
297 termsToSave.add(generalTerm);
298 }
299 }
300 }else{
301 //TODO Exception type
302 String message = "This term has specifing terms. Move or delete specifiing terms prior to delete or change delete configuration.";
303 result.addRelatedObjects(specificTerms);
304 result.setAbort();
305 Exception ex = new DataChangeNoRollbackException(message);
306 result.addException(ex);
307 }
308 }
309
310 //kind of
311 DefinedTermBase generalTerm = term.getKindOf();
312 if (generalTerm != null){
313 if (config.isDeleteKindOfRelations()){
314 generalTerm.removeGeneralization(term);
315 }else{
316 //TODO Exception type
317 String message = "This term is kind of another term. Move or delete kind of relationship prior to delete or change delete configuration.";
318 result.addRelatedObject(generalTerm);
319 result.setAbort();
320 DataChangeNoRollbackException ex = new DataChangeNoRollbackException(message);
321 result.addException(ex);
322 throw ex;
323 }
324 }
325
326 //part of
327 DefinedTermBase parentTerm = term.getPartOf();
328 if (parentTerm != null){
329 if (! config.isDeletePartOfRelations()){
330 //TODO Exception type
331 String message = "This term is included in another term. Remove from parent term prior to delete or change delete configuration.";
332 result.addRelatedObject(parentTerm);
333 result.setAbort();
334 DataChangeNoRollbackException ex = new DataChangeNoRollbackException(message);
335 result.addException(ex);
336 }
337 }
338
339
340 //included in
341 Set<DefinedTermBase> includedTerms = term.getIncludes();
342 if (includedTerms.size()> 0){
343 if (config.isDeleteIncludedRelations()){
344 DefinedTermBase parent = term.getPartOf();
345 for (DefinedTermBase includedTerm: includedTerms){
346 term.removeIncludes(includedTerm);
347 if (parent != null){
348 parent.addIncludes(includedTerm);
349 termsToSave.add(parent);
350 }
351 }
352 }else{
353 //TODO Exception type
354 String message = "This term includes other terms. Move or delete included terms prior to delete or change delete configuration.";
355 result.addRelatedObjects(includedTerms);
356 result.setAbort();
357 Exception ex = new DataChangeNoRollbackException(message);
358 result.addException(ex);
359 }
360 }
361
362 //part of
363 if (parentTerm != null){
364 if (config.isDeletePartOfRelations()){
365 parentTerm.removeIncludes(term);
366 termsToSave.add(parentTerm);
367 }else{
368 //handled before "included in"
369 }
370 }
371
372 if (result.isOk()){
373 TermVocabulary voc = term.getVocabulary();
374 if (voc!= null){
375 voc.removeTerm(term);
376 }
377 //TODO save voc
378 if (true){
379 dao.delete(term);
380 result.addDeletedObject(term);
381 dao.saveOrUpdateAll(termsToSave);
382 }
383 }
384 } catch (DataChangeNoRollbackException e) {
385 result.setStatus(Status.ERROR);
386 }
387 return result;
388 }
389
390 @Override
391 @Transactional(readOnly = false)
392 public DeleteResult delete(UUID termUuid, TermDeletionConfigurator config){
393 return delete(dao.load(termUuid), config);
394 }
395
396 @Override
397 @Transactional(readOnly = false)
398 public void updateTitleCache(Class<? extends DefinedTermBase> clazz, Integer stepSize, IIdentifiableEntityCacheStrategy<DefinedTermBase> cacheStrategy, IProgressMonitor monitor) {
399 //TODO shouldn't this be TermBase instead of DefinedTermBase
400 if (clazz == null){
401 clazz = DefinedTermBase.class;
402 }
403 super.updateTitleCacheImpl(clazz, stepSize, cacheStrategy, monitor);
404 }
405
406 @Override
407 public DeleteResult isDeletable(UUID termUuid, DeleteConfiguratorBase config){
408 TermDeletionConfigurator termConfig = null;
409 if(config instanceof TermDeletionConfigurator){
410 termConfig = (TermDeletionConfigurator) config;
411 }
412 DeleteResult result = new DeleteResult();
413 DefinedTermBase term = load(termUuid);
414 Set<CdmBase> references = commonService.getReferencingObjectsForDeletion(term);
415
416 if(termConfig!=null){
417 //generalization of
418 Set<DefinedTermBase> specificTerms = term.getGeneralizationOf();
419 if (!specificTerms.isEmpty() && termConfig.isDeleteGeneralizationOfRelations()){
420 references.removeAll(specificTerms);
421 }
422 //kind of
423 DefinedTermBase generalTerm = term.getKindOf();
424 if (generalTerm != null && termConfig.isDeleteKindOfRelations()){
425 references.remove(generalTerm);
426 }
427 //part of
428 DefinedTermBase parentTerm = term.getPartOf();
429 if (parentTerm != null && termConfig.isDeletePartOfRelations()){
430 references.remove(parentTerm);
431 }
432 //included in
433 Set<DefinedTermBase> includedTerms = term.getIncludes();
434 if (!includedTerms.isEmpty() && termConfig.isDeleteIncludedRelations()){
435 references.removeAll(includedTerms);
436 }
437 }
438
439 //gather remaining referenced objects
440 for (CdmBase relatedObject : references) {
441 if(relatedObject instanceof TermVocabulary){
442 continue;
443 }
444 result.getRelatedObjects().add(relatedObject);
445 String message = "An object of " + relatedObject.getClass().getName() + " with ID " + relatedObject.getId() + " is referencing the object" ;
446 result.addException(new ReferencedObjectUndeletableException(message));
447 result.setAbort();
448 }
449 return result;
450 }
451
452 @Override
453 @Transactional(readOnly = false)
454 public Map<UUID, Representation> saveOrUpdateRepresentations(Collection<Representation> representations){
455 return representationDao.saveOrUpdateAll(representations);
456 }
457
458 @Override
459 @Transactional(readOnly = true)
460 public List<UuidAndTitleCache<NamedArea>> getUuidAndTitleCache(List<TermVocabulary> vocs, Integer limit, String pattern, Language lang) {
461 List<NamedArea> areas = dao.getUuidAndTitleCache(vocs, limit, pattern);
462
463 List<UuidAndTitleCache<NamedArea>> result = new ArrayList();
464 UuidAndTitleCache<NamedArea> uuidAndTitleCache;
465 for (NamedArea area: areas){
466 uuidAndTitleCache = new UuidAndTitleCache<>(area.getUuid(), area.getId(), area.labelWithLevel(area, lang));
467 result.add(uuidAndTitleCache);
468 }
469
470 return result;
471 }
472
473 @Override
474 public Collection<TermDto> getIncludesAsDto(
475 TermDto parentTerm) {
476 return dao.getIncludesAsDto(parentTerm);
477 }
478
479 @Override
480 public Collection<TermDto> getKindOfsAsDto(
481 TermDto parentTerm) {
482 return dao.getKindOfsAsDto(parentTerm);
483 }
484
485 @Transactional(readOnly = false)
486 @Override
487 public void moveTerm(TermDto termDto, UUID parentUUID) {
488 moveTerm(termDto, parentUUID, null);
489 }
490
491 @SuppressWarnings({ "rawtypes", "unchecked" })
492 @Transactional(readOnly = false)
493 @Override
494 public void moveTerm(TermDto termDto, UUID parentUuid, TermMovePosition termMovePosition) {
495 boolean isKindOf = termDto.getKindOfUuid()!=null && termDto.getKindOfUuid().equals(parentUuid);
496 TermVocabulary vocabulary = HibernateProxyHelper.deproxy(vocabularyService.load(termDto.getVocabularyUuid()));
497 DefinedTermBase parent = HibernateProxyHelper.deproxy(dao.load(parentUuid));
498 if(parent==null){
499 //new parent is a vocabulary
500 TermVocabulary parentVocabulary = HibernateProxyHelper.deproxy(vocabularyService.load(parentUuid));
501 DefinedTermBase term = HibernateProxyHelper.deproxy(dao.load(termDto.getUuid()));
502 if(parentVocabulary!=null){
503 term.setKindOf(null);
504 term.setPartOf(null);
505
506 vocabulary.removeTerm(term);
507 parentVocabulary.addTerm(term);
508 }
509 vocabularyService.saveOrUpdate(parentVocabulary);
510 }
511 else {
512 DefinedTermBase term = HibernateProxyHelper.deproxy(dao.load(termDto.getUuid()));
513 //new parent is a term
514 if(parent.isInstanceOf(OrderedTermBase.class)
515 && term.isInstanceOf(OrderedTermBase.class)
516 && termMovePosition!=null
517 && HibernateProxyHelper.deproxy(parent, OrderedTermBase.class).getVocabulary().isInstanceOf(OrderedTermVocabulary.class)) {
518 //new parent is an ordered term
519 OrderedTermBase orderedTerm = HibernateProxyHelper.deproxy(term, OrderedTermBase.class);
520 OrderedTermBase targetOrderedDefinedTerm = HibernateProxyHelper.deproxy(parent, OrderedTermBase.class);
521 OrderedTermVocabulary otVoc = HibernateProxyHelper.deproxy(targetOrderedDefinedTerm.getVocabulary(), OrderedTermVocabulary.class);
522 if(termMovePosition.equals(TermMovePosition.BEFORE)) {
523 orderedTerm.getVocabulary().removeTerm(orderedTerm);
524 otVoc.addTermAbove(orderedTerm, targetOrderedDefinedTerm);
525 if (targetOrderedDefinedTerm.getPartOf() != null){
526 targetOrderedDefinedTerm.getPartOf().addIncludes(orderedTerm);
527 }
528 }
529 else if(termMovePosition.equals(TermMovePosition.AFTER)) {
530 orderedTerm.getVocabulary().removeTerm(orderedTerm);
531 otVoc.addTermBelow(orderedTerm, targetOrderedDefinedTerm);
532 if (targetOrderedDefinedTerm.getPartOf() != null){
533 targetOrderedDefinedTerm.getPartOf().addIncludes(orderedTerm);
534 }
535 }
536 else if(termMovePosition.equals(TermMovePosition.ON)) {
537 orderedTerm.getVocabulary().removeTerm(orderedTerm);
538 targetOrderedDefinedTerm.addIncludes(orderedTerm);
539 targetOrderedDefinedTerm.getVocabulary().addTerm(orderedTerm);
540 }
541 }
542 else{
543 vocabulary.removeTerm(term);
544 if(isKindOf){
545 parent.addGeneralizationOf(term);
546 }
547 else{
548 parent.addIncludes(term);
549 }
550 parent.getVocabulary().addTerm(term);
551 }
552 vocabularyService.saveOrUpdate(parent.getVocabulary());
553 }
554 }
555
556 @SuppressWarnings({ "rawtypes", "unchecked" })
557 @Transactional(readOnly = false)
558 @Override
559 public TermDto addNewTerm(TermType termType, UUID parentUUID, boolean isKindOf) {
560 DefinedTermBase term = termType.getEmptyDefinedTermBase();
561 dao.save(term);
562 DefinedTermBase parent = dao.load(parentUUID);
563 if(isKindOf){
564 parent.addGeneralizationOf(term);
565 }
566 else{
567 parent.addIncludes(term);
568 }
569 parent.getVocabulary().addTerm(term);
570 dao.saveOrUpdate(parent);
571 return TermDto.fromTerm(term, true);
572 }
573
574 public enum TermMovePosition{
575 BEFORE,
576 AFTER,
577 ON
578 }
579
580 }