View Javadoc
1   /*
2    * This file is part of ***  M y C o R e  ***
3    * See http://www.mycore.de/ for details.
4    *
5    * MyCoRe is free software: you can redistribute it and/or modify
6    * it under the terms of the GNU General Public License as published by
7    * the Free Software Foundation, either version 3 of the License, or
8    * (at your option) any later version.
9    *
10   * MyCoRe is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with MyCoRe.  If not, see <http://www.gnu.org/licenses/>.
17   */
18  
19  package org.mycore.datamodel.metadata;
20  
21  import java.util.ArrayList;
22  import java.util.Date;
23  import java.util.List;
24  import java.util.Optional;
25  import java.util.function.Consumer;
26  import java.util.stream.Collectors;
27  import java.util.stream.IntStream;
28  
29  import org.apache.commons.lang3.StringUtils;
30  import org.apache.logging.log4j.LogManager;
31  import org.apache.logging.log4j.Logger;
32  import org.jdom2.Element;
33  import org.mycore.common.MCRException;
34  import org.mycore.common.MCRUtils;
35  import org.mycore.common.config.MCRConfiguration2;
36  import org.mycore.datamodel.classifications2.MCRCategoryDAOFactory;
37  import org.mycore.datamodel.classifications2.MCRCategoryID;
38  import org.mycore.datamodel.common.MCRISO8601Date;
39  
40  import com.google.gson.JsonArray;
41  import com.google.gson.JsonObject;
42  
43  /**
44   * This class implements all methods for handling MCRObject service data.
45   * The service data stores technical information that is no metadata.
46   * The service data holds six types of data (dates, rules  flags, messages,
47   * classifications and states). The flags and messages are text strings
48   * and are optional.
49   * <p>
50   *
51   * The dates are represent by a date and a type. Two types are in service data
52   * at every time and can't remove:
53   * <ul>
54   * <li>createdate - for the creating date of the object, this was set only one
55   * time</li>
56   * <li>modifydate - for the accepting date of the object, this was set at every
57   * changes</li>
58   * </ul>
59   * Other date types are optional, but as example in Dublin Core:
60   * <ul>
61   * <li>submitdate - for the submiting date of the object</li>
62   * <li>acceptdate - for the accepting date of the object</li>
63   * <li>validfromdate - for the date of the object, at this the object is valid
64   * to use</li>
65   * <li>validtodate - for the date of the object, at this the object is no more
66   * valid to use</li>
67   * </ul>
68   *
69   * The state is optional and represented by a MyCoRe classification object.
70   *
71   * @author Jens Kupferschmidt
72   * @author Matthias Eichner
73   * @author Robert Stephan
74   * @version $Revision$ $Date$
75   */
76  public class MCRObjectService {
77  
78      private static final Logger LOGGER = LogManager.getLogger(MCRObjectService.class);
79  
80      /**
81       * constant for create date
82       */
83      public static final String DATE_TYPE_CREATEDATE = "createdate";
84  
85      /**
86       * constant for modify date
87       */
88      public static final String DATE_TYPE_MODIFYDATE = "modifydate";
89  
90      /**
91       * constant for create user
92       */
93      public static final String FLAG_TYPE_CREATEDBY = "createdby";
94  
95      /**
96       * constant for modify user
97       */
98      public static final String FLAG_TYPE_MODIFIEDBY = "modifiedby";
99  
100     private final ArrayList<MCRMetaISO8601Date> dates = new ArrayList<>();
101 
102     private final ArrayList<MCRMetaAccessRule> rules = new ArrayList<>();
103 
104     private final ArrayList<MCRMetaLangText> flags = new ArrayList<>();
105 
106     private final ArrayList<MCRMetaDateLangText> messages = new ArrayList<>();
107 
108     private final ArrayList<MCRMetaClassification> classifications = new ArrayList<>();
109 
110     private MCRCategoryID state;
111 
112     /**
113      * This is the constructor of the MCRObjectService class. All data are set
114      * to null.
115      */
116     public MCRObjectService() {
117 
118         Date now = new Date();
119 
120         MCRMetaISO8601Date createDate = new MCRMetaISO8601Date("servdate", DATE_TYPE_CREATEDATE, 0);
121         createDate.setDate(now);
122         dates.add(createDate);
123 
124         MCRMetaISO8601Date modifyDate = new MCRMetaISO8601Date("servdate", DATE_TYPE_MODIFYDATE, 0);
125         modifyDate.setDate(now);
126         dates.add(modifyDate);
127 
128     }
129 
130     /**
131      * This method read the XML input stream part from a DOM part for the
132      * structure data of the document.
133      *
134      * @param service
135      *            a list of relevant DOM elements for the metadata
136      */
137     public final void setFromDOM(Element service) {
138 
139         // date part
140         Element datesElement = service.getChild("servdates");
141         dates.clear();
142 
143         if (datesElement != null) {
144             List<Element> dateElements = datesElement.getChildren();
145             for (Element dateElement : dateElements) {
146                 String dateElementName = dateElement.getName();
147                 if (!"servdate".equals(dateElementName)) {
148                     continue;
149                 }
150                 MCRMetaISO8601Date date = new MCRMetaISO8601Date();
151                 date.setFromDOM(dateElement);
152                 setDate(date);
153             }
154         }
155 
156         // rule part
157         Element rulesElement = service.getChild("servacls");
158         if (rulesElement != null) {
159             List<Element> ruleElements = rulesElement.getChildren();
160             for (Element ruleElement : ruleElements) {
161                 if (!ruleElement.getName().equals("servacl")) {
162                     continue;
163                 }
164                 MCRMetaAccessRule user = new MCRMetaAccessRule();
165                 user.setFromDOM(ruleElement);
166                 rules.add(user);
167             }
168         }
169 
170         // flag part
171         Element flagsElement = service.getChild("servflags");
172         if (flagsElement != null) {
173             List<Element> flagElements = flagsElement.getChildren();
174             for (Element flagElement : flagElements) {
175                 if (!flagElement.getName().equals("servflag")) {
176                     continue;
177                 }
178                 MCRMetaLangText flag = new MCRMetaLangText();
179                 flag.setFromDOM(flagElement);
180                 flags.add(flag);
181             }
182         }
183 
184         // classification part
185         Element classificationsElement = service.getChild("servclasses");
186         if (classificationsElement != null) {
187             List<Element> classificationElements = classificationsElement.getChildren();
188             for (Element classificationElement : classificationElements) {
189                 if (!classificationElement.getName().equals("servclass")) {
190                     continue;
191                 }
192                 MCRMetaClassification classification = new MCRMetaClassification();
193                 classification.setFromDOM(classificationElement);
194                 classifications.add(classification);
195             }
196         }
197 
198         // nessage part
199         Element messagesElement = service.getChild("servmessages");
200         if (messagesElement != null) {
201             List<Element> messageElements = messagesElement.getChildren();
202             for (Element messageElement : messageElements) {
203                 if (!messageElement.getName().equals("servmessage")) {
204                     continue;
205                 }
206                 MCRMetaDateLangText message = new MCRMetaDateLangText();
207                 message.setFromDOM(messageElement);
208                 messages.add(message);
209             }
210         }
211 
212         // States part
213         Element statesElement = service.getChild("servstates");
214         if (statesElement != null) {
215             List<Element> stateElements = statesElement.getChildren();
216             for (Element stateElement : stateElements) {
217                 if (!stateElement.getName().equals("servstate")) {
218                     continue;
219                 }
220                 MCRMetaClassification stateClass = new MCRMetaClassification();
221                 stateClass.setFromDOM(stateElement);
222                 state = new MCRCategoryID(stateClass.getClassId(), stateClass.getCategId());
223             }
224         }
225     }
226 
227     /**
228      * This method return the size of the date list.
229      *
230      * @return the size of the date list
231      */
232     public final int getDateSize() {
233         return dates.size();
234     }
235 
236     /**
237      * Returns the dates.
238      *
239      * @return list of dates
240      */
241     protected ArrayList<MCRMetaISO8601Date> getDates() {
242         return dates;
243     }
244 
245     /**
246      * This method returns the status classification
247      *
248      * @return the status as MCRMetaClassification,
249      *         can return null
250      *
251      */
252     public final MCRCategoryID getState() {
253         return state;
254     }
255 
256     /**
257      * This method get a date for a given type. If the type was not found, an
258      * null was returned.
259      *
260      * @param type
261      *            the type of the date
262      * @return the date as GregorianCalendar
263      *
264      * @see MCRObjectService#DATE_TYPE_CREATEDATE
265      * @see MCRObjectService#DATE_TYPE_MODIFYDATE
266      */
267     public final Date getDate(String type) {
268         MCRMetaISO8601Date isoDate = getISO8601Date(type);
269         if (isoDate == null) {
270             return null;
271         }
272         return isoDate.getDate();
273     }
274 
275     private MCRMetaISO8601Date getISO8601Date(String type) {
276         if (type == null || type.length() == 0) {
277             return null;
278         }
279 
280         return IntStream.range(0, dates.size())
281             .mapToObj(dates::get)
282             .filter(d -> d.getType().equals(type))
283             .findAny()
284             .orElse(null);
285     }
286 
287     /**
288      * This method set a date element in the dates list to a actual date value.
289      * If the given type exists, the date was update.
290      *
291      * @param type
292      *            the type of the date
293      */
294     public final void setDate(String type) {
295         setDate(type, new Date());
296     }
297 
298     /**
299      * This method set a date element in the dates list to a given date value.
300      * If the given type exists, the date was update.
301      *
302      * @param type
303      *            the type of the date
304      * @param date
305      *            set time to this Calendar
306      *            null means the actual date
307      */
308     public final void setDate(String type, Date date) {
309         MCRMetaISO8601Date d = getISO8601Date(type); //search date in ArrayList
310         if (d == null) {
311             d = new MCRMetaISO8601Date("servdate", type, 0);
312             d.setDate(date);
313             dates.add(d);
314         } else {
315             d.setDate(date); // alter date found in ArrayList
316         }
317     }
318 
319     /**
320      * This method set a date element in the dates list to a given date value.
321      * If the given type exists, the date was update.
322      *
323      * @param date
324      *            set time to this Calendar
325      */
326     private void setDate(MCRMetaISO8601Date date) {
327         MCRMetaISO8601Date d = getISO8601Date(date.getType()); //search date in ArrayList
328 
329         if (d == null) {
330             dates.add(date);
331         } else {
332             d.setDate(date.getDate()); // alter date found in ArrayList
333         }
334     }
335 
336     /**
337      * This method removes the date of the specified type from
338      * the date list.
339      *
340      * @param type
341      *            a type as string
342      */
343     public final void removeDate(String type) {
344         if (DATE_TYPE_CREATEDATE.equals(type) || DATE_TYPE_MODIFYDATE.equals(type)) {
345             LOGGER.error("Cannot delete built-in date: " + type);
346         } else {
347             MCRMetaISO8601Date d = getISO8601Date(type);
348             if (d != null) {
349                 dates.remove(d);
350             }
351         }
352     }
353 
354     /**
355      * This method sets the status classification
356      */
357     public final void setState(MCRCategoryID state) {
358         if (state == null) {
359             this.state = state;
360         } else {
361             if (MCRCategoryDAOFactory.getInstance().exist(state)) {
362                 this.state = state;
363             } else {
364                 LOGGER.warn("Error at setting servstate classification.",
365                     new MCRException("The category " + state + " does not exist."));
366             }
367         }
368     }
369 
370     /**
371      * This method sets the status classification with the given string as categid
372      * and the default classid ('state')
373      */
374     public final void setState(String state) {
375         if (state == null) {
376             this.state = null;
377         } else {
378             MCRCategoryID categState = new MCRCategoryID(
379                 MCRConfiguration2.getString("MCR.Metadata.Service.State.Classification.ID").orElse("state"),
380                 state);
381             setState(categState);
382         }
383     }
384 
385     /**
386      * This method removes the current state
387      */
388     public final void removeState() {
389         this.state = null;
390     }
391 
392     /**
393      * This method add a flag to the flag list.
394      *
395      * @param value -
396      *            the new flag as string
397      */
398     public final void addFlag(String value) {
399         addFlag(null, value);
400     }
401 
402     /**
403      * This method adds a flag to the flag list.
404      *
405      * @param type
406      *              a type as string
407      * @param value
408      *              the new flag value as string
409      */
410     public final void addFlag(String type, String value) {
411         String lType = StringUtils.trim(type);
412         MCRUtils.filterTrimmedNotEmpty(value)
413             .map(flagValue -> new MCRMetaLangText("servflag", null, lType, 0, null, flagValue))
414             .ifPresent(flags::add);
415     }
416 
417     /**
418      * This method get all flags from the flag list as a string.
419      *
420      * @return the flags string
421      */
422     public final String getFlags() {
423         StringBuilder sb = new StringBuilder();
424 
425         for (MCRMetaLangText flag : flags) {
426             sb.append(flag.getText()).append(" ");
427         }
428 
429         return sb.toString();
430     }
431 
432     /**
433      * Returns the flags as list.
434      *
435      * @return flags as list
436      */
437     protected final List<MCRMetaLangText> getFlagsAsList() {
438         return flags;
439     }
440 
441     /**
442      * This method returns all flag values of the specified type.
443      *
444      * @param type
445      *              a type as string.
446      * @return a list of flag values
447      */
448     protected final ArrayList<MCRMetaLangText> getFlagsAsMCRMetaLangText(String type) {
449         return flags.stream()
450             .filter(metaLangText -> StringUtils.equals(type, metaLangText.getType()))
451             .collect(Collectors.toCollection(ArrayList::new));
452     }
453 
454     /**
455      * This method returns all flag values of the specified type.
456      *
457      * @param type
458      *              a type as string.
459      * @return a list of flag values
460      */
461     public final ArrayList<String> getFlags(String type) {
462         return getFlagsAsMCRMetaLangText(type).stream()
463             .map(MCRMetaLangText::getText)
464             .collect(Collectors.toCollection(ArrayList::new));
465     }
466 
467     /**
468      * This method return the size of the flag list.
469      *
470      * @return the size of the flag list
471      */
472     public final int getFlagSize() {
473         return flags.size();
474     }
475 
476     /**
477      * This method get a single flag from the flag list as a string.
478      *
479      * @exception IndexOutOfBoundsException
480      *                throw this exception, if the index is invalid
481      * @return a flag string
482      */
483     public final String getFlag(int index) throws IndexOutOfBoundsException {
484         if (index < 0 || index >= flags.size()) {
485             throw new IndexOutOfBoundsException("Index error in getFlag.");
486         }
487         return flags.get(index).getText();
488     }
489 
490     /**
491      * This method gets a single flag type from the flag list as a string.
492      *
493      * @exception IndexOutOfBoundsException
494      *                throw this exception, if the index is invalid
495      * @return a flag type
496      */
497     public final String getFlagType(int index) throws IndexOutOfBoundsException {
498         if (index < 0 || index >= flags.size()) {
499             throw new IndexOutOfBoundsException("Index error in getFlagType.");
500         }
501         return flags.get(index).getType();
502     }
503 
504     /**
505      * This method return a boolean value if the given flag is set or not.
506      *
507      * @param value
508      *            a searched flag
509      * @return true if the flag was found in the list
510      */
511     public final boolean isFlagSet(String value) {
512         return MCRUtils.filterTrimmedNotEmpty(value)
513             .map(flagValue -> flags.stream().anyMatch(flag -> flagValue.equals(flag.getText())))
514             .orElse(false);
515     }
516 
517     /**
518      * Proves if the type is set in the flag list.
519      * @param type
520      *              a type as string
521      * @return  true if the flag list contains flags with this type,
522      *          otherwise false
523      */
524     public final boolean isFlagTypeSet(String type) {
525         return flags.stream().anyMatch(flag -> StringUtils.equals(type, flag.getType()));
526     }
527 
528     /**
529      * This method remove a flag from the flag list.
530      *
531      * @param index
532      *            a index in the list
533      * @exception IndexOutOfBoundsException
534      *                throw this exception, if the index is invalid
535      */
536     public final void removeFlag(int index) throws IndexOutOfBoundsException {
537         if (index < 0 || index >= flags.size()) {
538             throw new IndexOutOfBoundsException("Index error in removeFlag.");
539         }
540 
541         flags.remove(index);
542     }
543 
544     /**
545      * This method removes all flags of the specified type from
546      * the flag list.
547      *
548      * @param type
549      *            a type as string
550      */
551     public final void removeFlags(String type) {
552         flags.removeIf(f -> StringUtils.equals(type,  f.getType()));
553     }
554 
555     /**
556      * This method set a flag in the flag list.
557      *
558      * @param index
559      *            a index in the list
560      * @param value
561      *            the value of a flag as string
562      * @exception IndexOutOfBoundsException
563      *                throw this exception, if the index is invalid
564      */
565     public final void replaceFlag(int index, String value) throws IndexOutOfBoundsException {
566         MCRUtils.filterTrimmedNotEmpty(value)
567             .ifPresent(flagValue -> updateFlag(index, flag -> flag.setText(value)));
568     }
569 
570     private void updateFlag(int index, Consumer<MCRMetaLangText> flagUpdater) {
571         MCRMetaLangText flag = flags.get(index);
572         flagUpdater.accept(flag);
573     }
574 
575     /**
576      * This method sets the type value of a flag at the specified index.
577      *
578      * @param index
579      *            a index in the list
580      * @param type
581      *            the type a flag as string
582      * @exception IndexOutOfBoundsException
583      *                throw this exception, if the index is invalid
584      */
585     public final void replaceFlagType(int index, String type) throws IndexOutOfBoundsException {
586         String lType = StringUtils.trim(type);
587         updateFlag(index, flag -> flag.setType(lType));
588     }
589 
590     /**
591      * This method add a rule to the rules list.
592      *
593      * @param permission -
594      *            the new permission as string
595      * @param condition -
596      *            the new rule as JDOM tree Element
597      */
598     public final void addRule(String permission, Element condition) {
599         if (condition == null) {
600             return;
601         }
602         MCRUtils.filterTrimmedNotEmpty(permission)
603             .filter(p -> getRuleIndex(p) == -1)
604             .map(p -> new MCRMetaAccessRule("servacl", null, 0, p, condition))
605             .ifPresent(rules::add);
606     }
607 
608     /**
609      * This method return the size of the rules list.
610      *
611      * @return the size of the rules list
612      */
613     public final int getRulesSize() {
614         return rules.size();
615     }
616 
617     /**
618      * This method return the index of a permission in the rules list.
619      *
620      * @return the index of a permission in the rules list
621      */
622     public final int getRuleIndex(String permission) {
623         int notFound = -1;
624         if (permission == null || permission.trim().length() == 0) {
625             return notFound;
626         }
627         return IntStream.range(0, rules.size())
628             .filter(i -> rules.get(i).getPermission().equals(permission))
629             .findAny()
630             .orElse(notFound);
631     }
632 
633     /**
634      * This method get a single rule from the rules list as a JDOM Element.
635      *
636      * @exception IndexOutOfBoundsException
637      *                throw this exception, if the index is invalid
638      * @return a the MCRMetaAccessRule instance
639      */
640     public final MCRMetaAccessRule getRule(int index) throws IndexOutOfBoundsException {
641         if (index < 0 || index >= rules.size()) {
642             throw new IndexOutOfBoundsException("Index error in getRule.");
643         }
644         return rules.get(index);
645     }
646 
647     /**
648      * This method get a single permission name of rule from the rules list as a
649      * string.
650      *
651      * @exception IndexOutOfBoundsException
652      *                throw this exception, if the index is invalid
653      * @return a rule permission string
654      */
655     public final String getRulePermission(int index) throws IndexOutOfBoundsException {
656         if (index < 0 || index >= rules.size()) {
657             throw new IndexOutOfBoundsException("Index error in getRulePermission.");
658         }
659         return rules.get(index).getPermission();
660     }
661 
662     /**
663      * This method remove a rule from the rules list.
664      *
665      * @param index
666      *            a index in the list
667      * @exception IndexOutOfBoundsException
668      *                throw this exception, if the index is invalid
669      */
670     public final void removeRule(int index) throws IndexOutOfBoundsException {
671         if (index < 0 || index >= rules.size()) {
672             throw new IndexOutOfBoundsException("Index error in removeRule.");
673         }
674         rules.remove(index);
675     }
676 
677     /**
678      * Returns the rules.
679      *
680      * @return list of rules
681      */
682     protected final ArrayList<MCRMetaAccessRule> getRules() {
683         return rules;
684     }
685 
686     /**
687      * This method create a XML stream for all structure data.
688      *
689      * @exception MCRException
690      *                if the content of this class is not valid
691      * @return a JDOM Element with the XML data of the structure data part
692      */
693     public final Element createXML() throws MCRException {
694         try {
695             validate();
696         } catch (MCRException exc) {
697             throw new MCRException("The content is not valid.", exc);
698         }
699         Element elm = new Element("service");
700 
701         if (dates.size() != 0) {
702             Element elmm = new Element("servdates");
703             elmm.setAttribute("class", "MCRMetaISO8601Date");
704 
705             for (MCRMetaISO8601Date date : dates) {
706                 elmm.addContent(date.createXML());
707             }
708 
709             elm.addContent(elmm);
710         }
711 
712         if (rules.size() != 0) {
713             Element elmm = new Element("servacls");
714             elmm.setAttribute("class", "MCRMetaAccessRule");
715 
716             for (MCRMetaAccessRule rule : rules) {
717                 elmm.addContent(rule.createXML());
718             }
719 
720             elm.addContent(elmm);
721         }
722 
723         if (flags.size() != 0) {
724             Element elmm = new Element("servflags");
725             elmm.setAttribute("class", "MCRMetaLangText");
726 
727             for (MCRMetaLangText flag : flags) {
728                 elmm.addContent(flag.createXML());
729             }
730 
731             elm.addContent(elmm);
732         }
733 
734         if (messages.size() != 0) {
735             Element elmm = new Element("servmessages");
736             elmm.setAttribute("class", "MCRMetaDateLangText");
737 
738             for (MCRMetaDateLangText message : messages) {
739                 elmm.addContent(message.createXML());
740             }
741 
742             elm.addContent(elmm);
743         }
744 
745         if (classifications.size() != 0) {
746             Element elmm = new Element("servclasses");
747             elmm.setAttribute("class", "MCRMetaClassification");
748 
749             for (MCRMetaClassification classification : classifications) {
750                 elmm.addContent(classification.createXML());
751             }
752 
753             elm.addContent(elmm);
754         }
755 
756         if (state != null) {
757             Element elmm = new Element("servstates");
758             elmm.setAttribute("class", "MCRMetaClassification");
759             MCRMetaClassification stateClass = new MCRMetaClassification("servstate", 0, null, state);
760             elmm.addContent(stateClass.createXML());
761             elm.addContent(elmm);
762         }
763 
764         return elm;
765     }
766 
767     /**
768      * Creates the JSON representation of this service.
769      *
770      * <pre>
771      *   {
772      *      dates: [
773      *          {@link MCRMetaISO8601Date#createJSON()},
774      *          ...
775      *      ],
776      *      rules: [
777      *          {@link MCRMetaAccessRule#createJSON()},
778      *          ...
779      *      ],
780      *      flags: [
781      *          {@link MCRMetaLangText#createJSON()},
782      *          ...
783      *      ],
784      *      messages: [
785      *          {@link MCRMetaDateLangText#createJSON()},
786      *          ...
787      *      ],
788      *      classifications: [
789      *          {@link MCRMetaClassification#createJSON()},
790      *          ...
791      *      ],
792      *      state: {
793      *
794      *      }
795      *   }
796      * </pre>
797      *
798      * @return a json gson representation of this service
799      */
800     public final JsonObject createJSON() {
801         JsonObject service = new JsonObject();
802 
803         // dates
804         if (!getDates().isEmpty()) {
805             JsonObject dates = new JsonObject();
806             getDates()
807                 .stream()
808                 .forEachOrdered(date -> {
809                     JsonObject jsonDate = date.createJSON();
810                     jsonDate.remove("type");
811                     dates.add(date.getType(), jsonDate);
812                 });
813             service.add("dates", dates);
814         }
815 
816         // rules
817         if (!getRules().isEmpty()) {
818             JsonArray rules = new JsonArray();
819             getRules()
820                 .stream()
821                 .map(MCRMetaAccessRule::createJSON)
822                 .forEachOrdered(rules::add);
823             service.add("rules", rules);
824         }
825 
826         // flags
827         if (!getFlags().isEmpty()) {
828             JsonArray flags = new JsonArray();
829             getFlagsAsList()
830                 .stream()
831                 .map(MCRMetaLangText::createJSON)
832                 .forEachOrdered(flags::add);
833             service.add("flags", flags);
834         }
835 
836         // messages
837         if (!getMessages().isEmpty()) {
838             JsonArray messages = new JsonArray();
839             getMessagesAsList()
840                 .stream()
841                 .map(MCRMetaDateLangText::createJSON)
842                 .forEachOrdered(messages::add);
843             service.add("messages", messages);
844         }
845 
846         // classifications
847         if (!getClassifications().isEmpty()) {
848             JsonArray classifications = new JsonArray();
849             getClassificationsAsList()
850                 .stream()
851                 .map(MCRMetaClassification::createJSON)
852                 .forEachOrdered(classifications::add);
853             service.add("classifications", classifications);
854         }
855 
856         // state
857         Optional.ofNullable(getState()).ifPresent(stateId -> {
858             JsonObject state = new JsonObject();
859             if (stateId.getID() != null) {
860                 state.addProperty("id", stateId.getID());
861             }
862             state.addProperty("rootId", stateId.getRootID());
863         });
864 
865         return service;
866     }
867 
868     /**
869      * This method check the validation of the content of this class. The method
870      * returns <em>true</em> if
871      * <ul>
872      * <li>the date value of "createdate" is not null or empty
873      * <li>the date value of "modifydate" is not null or empty
874      * </ul>
875      * otherwise the method return <em>false</em>
876      *
877      * @return a boolean value
878      */
879     public final boolean isValid() {
880         try {
881             validate();
882             return true;
883         } catch (MCRException exc) {
884             LOGGER.warn("The <service> part is invalid.");
885         }
886         return false;
887     }
888 
889     /**
890      * Validates the content of this class. This method throws an exception if:
891      *  <ul>
892      *  <li>the date value of "createdate" is not null or empty</li>
893      *  <li>the date value of "modifydate" is not null or empty</li>
894      *  </ul>
895      *
896      * @throws MCRException the content is invalid
897      */
898     public void validate() {
899         // TODO: this makes no sense - there is nothing to validate
900         if (getISO8601Date(DATE_TYPE_CREATEDATE) == null) {
901             setDate(DATE_TYPE_CREATEDATE);
902         }
903         if (getISO8601Date(DATE_TYPE_MODIFYDATE) == null) {
904             setDate(DATE_TYPE_MODIFYDATE);
905         }
906     }
907 
908     /**
909      * This method returns the index for the given flag value.
910      *
911      * @param value
912      *            the value of a flag as string
913      * @return the index number or -1 if the value was not found
914      */
915     public final int getFlagIndex(String value) {
916         return MCRUtils.filterTrimmedNotEmpty(value)
917             .map(v -> {
918                 for (int i = 0; i < flags.size(); i++) {
919                     if (flags.get(i).getText().equals(v)) {
920                         return i;
921                     }
922                 }
923                 return -1;
924             })
925             .orElse(-1);
926     }
927 
928     /**
929      * This method return the size of the message list.
930      *
931      * @return the size of the message list
932      */
933     public final int getMessagesSize() {
934         return messages.size();
935     }
936 
937     /**
938      * Returns the messages as list.
939      *
940      * @return messages as list
941      */
942     protected final ArrayList<MCRMetaDateLangText> getMessagesAsList() {
943         return messages;
944     }
945 
946     /**
947      * This method returns all message values of the specified type.
948      *
949      * @param type
950      *              a type as string.
951      * @return a list of message values
952      */
953     protected final ArrayList<MCRMetaDateLangText> getMessagesAsMCRMetaDateLangText(String type) {
954         return messages.stream()
955             .filter(metaLangText -> type.equals(metaLangText.getType()))
956             .collect(Collectors.toCollection(ArrayList::new));
957     }
958 
959     /**
960      * This method returns all messages values of any type.
961      *
962      * @return a list of message values
963      */
964     public final ArrayList<String> getMessages() {
965         return messages.stream()
966             .map(MCRMetaDateLangText::getText)
967             .collect(Collectors.toCollection(ArrayList::new));
968     }
969 
970     /**
971      * This method returns all messages values of the specified type.
972      *
973      * @param type
974      *              a type as string.
975      * @return a list of message values
976      */
977     public final ArrayList<String> getMessages(String type) {
978         return getMessagesAsMCRMetaDateLangText(type).stream()
979             .map(MCRMetaDateLangText::getText)
980             .collect(Collectors.toCollection(ArrayList::new));
981     }
982 
983     /**
984      * This method get a single messages from the message list as a string.
985      *
986      * @exception IndexOutOfBoundsException
987      *                throw this exception, if the index is invalid
988      * @return a message string
989      */
990     public final String getMessage(int index) throws IndexOutOfBoundsException {
991         if (index < 0 || index >= messages.size()) {
992             throw new IndexOutOfBoundsException("Index error in getMessage.");
993         }
994         return messages.get(index).getText();
995     }
996 
997     /**
998      * This method gets a single message type from the message list as a string.
999      *
1000      * @exception IndexOutOfBoundsException
1001      *                throw this exception, if the index is invalid
1002      * @return a message type
1003      */
1004     public final String getMessageType(int index) throws IndexOutOfBoundsException {
1005         if (index < 0 || index >= messages.size()) {
1006             throw new IndexOutOfBoundsException("Index error in getMessageType.");
1007         }
1008         return messages.get(index).getType();
1009     }
1010 
1011     /**
1012      * This method gets a single message date from the message list as a {@link MCRISO8601Date}.
1013      *
1014      * @exception IndexOutOfBoundsException
1015      *                throw this exception, if the index is invalid
1016      * @return a message value
1017      */
1018     public final MCRISO8601Date getMessageDate(int index) throws IndexOutOfBoundsException {
1019         if (index < 0 || index >= messages.size()) {
1020             throw new IndexOutOfBoundsException("Index error in getMessageMCRMetaLangText.");
1021         }
1022         return messages.get(index).getDate();
1023     }
1024 
1025     /**
1026      * This method add a message to the flag list.
1027      *
1028      * @param value -
1029      *            the new messages as string
1030      */
1031     public final void addMessage(String value) {
1032         addMessage(null, value);
1033     }
1034 
1035     /**
1036      * This method adds a message to the message list.
1037      *
1038      * @param type
1039      *              a type as string
1040      * @param value
1041      *              the new message value as string
1042      */
1043     public final void addMessage(String type, String value) {
1044         addMessage(type, value, null);
1045     }
1046 
1047     /**
1048      * This method adds a message to the message list.
1049      *
1050      * @param type
1051      *              a type as string
1052      * @param value
1053      *              the new message value as string
1054      * @param form
1055      *              a form as string, defaults to 'plain'
1056      */
1057     public final void addMessage(String type, String value, String form) {
1058         String lType = StringUtils.trim(type);
1059         MCRUtils.filterTrimmedNotEmpty(value)
1060             .map(messageValue -> {
1061                 MCRMetaDateLangText message
1062                     = new MCRMetaDateLangText("servmessage", null, lType, 0, form, messageValue);
1063                 message.setDate(MCRISO8601Date.now());
1064                 return message;
1065             })
1066             .ifPresent(messages::add);
1067     }
1068 
1069     /**
1070      * This method set a message in the message list.
1071      *
1072      * @param index
1073      *            a index in the list
1074      * @param value
1075      *            the value of a message as string
1076      * @exception IndexOutOfBoundsException
1077      *                throw this exception, if the index is invalid
1078      */
1079     public final void replaceMessage(int index, String value) throws IndexOutOfBoundsException {
1080         MCRUtils.filterTrimmedNotEmpty(value)
1081             .ifPresent(messageValue -> updateMessage(index, message -> message.setText(value)));
1082     }
1083 
1084     /**
1085      * This method sets the type value of a message at the specified index.
1086      *
1087      * @param index
1088      *            a index in the list
1089      * @param type
1090      *            the type of a message as string
1091      * @exception IndexOutOfBoundsException
1092      *                throw this exception, if the index is invalid
1093      */
1094     public final void replaceMessageType(int index, String type) throws IndexOutOfBoundsException {
1095         String lType = StringUtils.trim(type);
1096         updateMessage(index, message -> message.setType(lType));
1097     }
1098 
1099     private void updateMessage(int index, Consumer<MCRMetaDateLangText> messageUpdater) {
1100         MCRMetaDateLangText message = messages.get(index);
1101         messageUpdater.accept(message);
1102     }
1103 
1104     /**
1105      * This method remove a message from the message list.
1106      *
1107      * @param index
1108      *            a index in the list
1109      * @exception IndexOutOfBoundsException
1110      *                throw this exception, if the index is invalid
1111      */
1112     public final void removeMessage(int index) throws IndexOutOfBoundsException {
1113         if (index < 0 || index >= messages.size()) {
1114             throw new IndexOutOfBoundsException("Index error in removeMessage.");
1115         }
1116         messages.remove(index);
1117     }
1118 
1119     /**
1120      * This method removes all messages of the specified type from
1121      * the message list.
1122      *
1123      * @param type
1124      *            a type as string
1125      */
1126     public final void removeMessages(String type) {
1127         List<MCRMetaDateLangText> internalList = getMessagesAsMCRMetaDateLangText(type);
1128         messages.removeAll(internalList);
1129     }
1130 
1131     /**
1132      * This method return the size of the classification list.
1133      *
1134      * @return the size of the classification list
1135      */
1136     public final int getClassificationsSize() {
1137         return this.classifications.size();
1138     }
1139 
1140     /**
1141      * This method returns all classification values.
1142      *
1143      * @return classifications as list
1144      */
1145     protected final ArrayList<MCRMetaClassification> getClassificationsAsList() {
1146         return classifications;
1147     }
1148 
1149     /**
1150      * This method returns all classification values of the specified type.
1151      *
1152      * @param type
1153      *              a type as string.
1154      * @return a list of classification values
1155      */
1156     protected final ArrayList<MCRMetaClassification> getClassificationsAsMCRMetaClassification(String type) {
1157         return classifications.stream()
1158             .filter(metaLangText -> type.equals(metaLangText.getType()))
1159             .collect(Collectors.toCollection(ArrayList::new));
1160     }
1161 
1162     /**
1163      * This method returns all classification values of any type.
1164      *
1165      * @return a list of classification values
1166      */
1167     public final ArrayList<MCRCategoryID> getClassifications() {
1168         return classifications.stream()
1169             .map(c -> new MCRCategoryID(c.getClassId(), c.getCategId()))
1170             .collect(Collectors.toCollection(ArrayList::new));
1171     }
1172 
1173     /**
1174      * This method returns all classification values of the specified type.
1175      *
1176      * @param type
1177      *              a type as string.
1178      * @return a list of classification values
1179      */
1180     public final ArrayList<MCRCategoryID> getClassifications(String type) {
1181         return getClassificationsAsMCRMetaClassification(type).stream()
1182             .map(c -> new MCRCategoryID(c.getClassId(), c.getCategId()))
1183             .collect(Collectors.toCollection(ArrayList::new));
1184     }
1185 
1186     /**
1187      * This method get a single classification from the classification list as a string.
1188      *
1189      * @exception IndexOutOfBoundsException
1190      *                throw this exception, if the index is invalid
1191      * @return a classification string
1192      */
1193     public final MCRCategoryID getClassification(int index) throws IndexOutOfBoundsException {
1194         if (index < 0 || index >= classifications.size()) {
1195             throw new IndexOutOfBoundsException("Index error in getClassification.");
1196         }
1197         MCRMetaClassification classification = classifications.get(index);
1198         return new MCRCategoryID(classification.getCategId(), classification.getClassId());
1199     }
1200 
1201     /**
1202      * This method gets a single classification type from the classification list as a string.
1203      *
1204      * @exception IndexOutOfBoundsException
1205      *                throw this exception, if the index is invalid
1206      * @return a classification type
1207      */
1208     public final String getClassificationType(int index) throws IndexOutOfBoundsException {
1209         if (index < 0 || index >= classifications.size()) {
1210             throw new IndexOutOfBoundsException("Index error in getClassification.");
1211         }
1212         return classifications.get(index).getType();
1213     }
1214 
1215     /**
1216      * This method get a single classification from the classification list as.
1217      *
1218      * @exception IndexOutOfBoundsException
1219      *                throw this exception, if the index is invalid
1220      * @return a classification value
1221      */
1222     public final MCRMetaClassification getClassificationAsMCRMetaClassification(int index)
1223         throws IndexOutOfBoundsException {
1224         if (index < 0 || index >= classifications.size()) {
1225             throw new IndexOutOfBoundsException("Index error in getClassificationAsMCRMetaClassification.");
1226         }
1227         return classifications.get(index);
1228     }
1229 
1230     /**
1231      * This method adds a classification to the classification list.
1232      *
1233      * @param value -
1234      *            the new classification
1235      */
1236     public final void addClassification(MCRCategoryID value) {
1237         addClassification(null, value);
1238     }
1239 
1240     /**
1241      * This method adds a classification to the classification list.
1242      *
1243      * @param type
1244      *              a type as string
1245      * @param value
1246      *              the new classification value as {@link MCRCategoryID}
1247      */
1248     public final void addClassification(String type, MCRCategoryID value) {
1249         String lType = StringUtils.trim(type);
1250         classifications.add(new MCRMetaClassification("servclass", 0, lType, value));
1251     }
1252 
1253     /**
1254      * This method set a classification in the classification list.
1255      *
1256      * @param index
1257      *            a index in the list
1258      * @param value
1259      *            the classification as {@link MCRCategoryID}
1260      * @exception IndexOutOfBoundsException
1261      *                throw this exception, if the index is invalid
1262      */
1263     public final void replaceClassification(int index, MCRCategoryID value) throws IndexOutOfBoundsException {
1264         updateClassification(index,
1265             classificationValue -> classificationValue.setValue(value.getRootID(), value.getID()));
1266     }
1267 
1268     /**
1269      * This method sets the type value of a classification at the specified index.
1270      *
1271      * @param index
1272      *            a index in the list
1273      * @param type
1274      *            the type of a flag as string
1275      * @exception IndexOutOfBoundsException
1276      *                throw this exception, if the index is invalid
1277      */
1278     public final void replaceClassificationType(int index, String type) throws IndexOutOfBoundsException {
1279         String lType = StringUtils.trim(type);
1280         updateClassification(index, classification -> classification.setType(lType));
1281     }
1282 
1283     private void updateClassification(int index, Consumer<MCRMetaClassification> classificationUpdater) {
1284         MCRMetaClassification classification = classifications.get(index);
1285         classificationUpdater.accept(classification);
1286     }
1287 
1288     /**
1289      * This method remove a classification from the classification list.
1290      *
1291      * @param index
1292      *            a index in the list
1293      * @exception IndexOutOfBoundsException
1294      *                throw this exception, if the index is invalid
1295      */
1296     public final void removeClassification(int index) throws IndexOutOfBoundsException {
1297         if (index < 0 || index >= classifications.size()) {
1298             throw new IndexOutOfBoundsException("Index error in removeClassification.");
1299         }
1300         classifications.remove(index);
1301     }
1302 
1303     /**
1304      * This method removes all classification with the specified type from
1305      * the classification list.
1306      *
1307      * @param type
1308      *            a type as string
1309      */
1310     public final void removeClassifications(String type) {
1311         String lType = StringUtils.trim(type);
1312         classifications.removeIf(c -> StringUtils.equals(lType,  c.getType()));
1313     }
1314 
1315 }