xref: /OpenGrok/opengrok-indexer/src/main/java/org/opengrok/indexer/web/messages/Message.java (revision aa6abf429bacc2c0baa482bff3022e77ef23c183)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * See LICENSE.txt included in this distribution for the specific
9  * language governing permissions and limitations under the License.
10  *
11  * When distributing Covered Code, include this CDDL HEADER in each
12  * file and include the License file at LICENSE.txt.
13  * If applicable, add the following below this CDDL HEADER, with the
14  * fields enclosed by brackets "[]" replaced with your own identifying
15  * information: Portions Copyright [yyyy] [name of copyright owner]
16  *
17  * CDDL HEADER END
18  */
19 
20 /*
21  * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved.
22  */
23 package org.opengrok.indexer.web.messages;
24 
25 import com.fasterxml.jackson.core.JsonGenerator;
26 import com.fasterxml.jackson.core.JsonParser;
27 import com.fasterxml.jackson.databind.DeserializationContext;
28 import com.fasterxml.jackson.databind.SerializerProvider;
29 import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
30 import com.fasterxml.jackson.databind.annotation.JsonSerialize;
31 import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
32 import com.fasterxml.jackson.databind.ser.std.StdSerializer;
33 import jakarta.validation.constraints.NotBlank;
34 import jakarta.validation.constraints.NotEmpty;
35 import org.opengrok.indexer.web.Util;
36 import org.opengrok.indexer.web.api.constraints.PositiveDuration;
37 
38 import java.io.IOException;
39 import java.time.Duration;
40 import java.time.format.DateTimeParseException;
41 import java.util.Collections;
42 import java.util.Comparator;
43 import java.util.Locale;
44 import java.util.Objects;
45 import java.util.Set;
46 import java.util.TreeSet;
47 
48 import static org.opengrok.indexer.web.messages.MessagesContainer.MESSAGES_MAIN_PAGE_TAG;
49 
50 public class Message implements Comparable<Message>, JSONable {
51 
52     @NotEmpty(message = "tags cannot be empty")
53     private Set<String> tags = Collections.singleton(MESSAGES_MAIN_PAGE_TAG);
54 
55     public enum MessageLevel {
56         /**
57          * Known values: {@code SUCCESS}, {@code INFO}, {@code WARNING}, {@code ERROR}.
58          * The values are sorted according to their level. Higher numeric value of the level (i.e. the enum ordinal)
59          * means higher priority.
60          */
61         SUCCESS("success"), INFO("info"), WARNING("warning"), ERROR("error");
62 
63         private final String messageLevelString;
64 
MessageLevel(String str)65         MessageLevel(String str) {
66             messageLevelString = str;
67         }
68 
fromString(String val)69         public static MessageLevel fromString(String val) throws IllegalArgumentException {
70             for (MessageLevel v : MessageLevel.values()) {
71                 if (v.toString().equals(val.toLowerCase(Locale.ROOT))) {
72                     return v;
73                 }
74             }
75             throw new IllegalArgumentException("class type does not match any known value");
76         }
77 
78         @Override
toString()79         public String toString() {
80             return messageLevelString;
81         }
82 
83         @SuppressWarnings("rawtypes")
84         public static final Comparator<MessageLevel> VALUE_COMPARATOR = Comparator.comparingInt(Enum::ordinal);
85     }
86 
87     @JsonDeserialize(using = MessageLevelDeserializer.class)
88     @JsonSerialize(using = MessageLevelSerializer.class)
89     private MessageLevel messageLevel = MessageLevel.INFO;
90 
91     @NotBlank(message = "text cannot be empty")
92     @JsonSerialize(using = HTMLSerializer.class)
93     private String text;
94 
95     @PositiveDuration(message = "duration must be positive")
96     @JsonSerialize(using = DurationSerializer.class)
97     @JsonDeserialize(using = DurationDeserializer.class)
98     private Duration duration = Duration.ofMinutes(10);
99 
Message()100     private Message() { // needed for deserialization
101     }
102 
Message( final String text, final Set<String> tags, final MessageLevel messageLevel, final Duration duration )103     public Message(
104             final String text,
105             final Set<String> tags,
106             final MessageLevel messageLevel,
107             final Duration duration
108     ) {
109         if (text == null || text.isEmpty()) {
110             throw new IllegalArgumentException("text cannot be null or empty");
111         }
112         if (tags == null || tags.isEmpty()) {
113             throw new IllegalArgumentException("tags cannot be null or empty");
114         }
115         if (duration == null || duration.isNegative()) {
116             throw new IllegalArgumentException("duration cannot be null or negative");
117         }
118 
119         this.text = text;
120         this.tags = tags;
121         this.messageLevel = messageLevel;
122         this.duration = duration;
123     }
124 
getTags()125     public Set<String> getTags() {
126         return tags;
127     }
128 
getMessageLevel()129     public MessageLevel getMessageLevel() {
130         return messageLevel;
131     }
132 
getText()133     public String getText() {
134         return text;
135     }
136 
getDuration()137     public Duration getDuration() {
138         return duration;
139     }
140 
141     /**
142      * @param t set of tags
143      * @return true if message has at least one of the tags
144      */
hasAny(Set<String> t)145     public boolean hasAny(Set<String> t) {
146         Set<String> tmp = new TreeSet<>(t);
147         tmp.retainAll(tags);
148         return !tmp.isEmpty();
149     }
150 
151     /**
152      * @param tags set of tags to check
153      * @param text message text
154      * @return true if a mesage has at least one of the tags and text
155      */
hasTagsAndText(Set<String> tags, String text)156     public boolean hasTagsAndText(Set<String> tags, String text) {
157         if (text == null || text.isEmpty()) {
158             return hasAny(tags);
159         }
160 
161         return hasAny(tags) && getText().equals(text);
162     }
163 
164     @Override
equals(Object o)165     public boolean equals(Object o) {
166         if (this == o) {
167             return true;
168         }
169         if (o == null || getClass() != o.getClass()) {
170             return false;
171         }
172         Message message = (Message) o;
173         return Objects.equals(tags, message.tags) &&
174                 Objects.equals(messageLevel, message.messageLevel) &&
175                 Objects.equals(text, message.text) &&
176                 Objects.equals(duration, message.duration);
177     }
178 
179     @Override
hashCode()180     public int hashCode() {
181         return Objects.hash(tags, messageLevel, text, duration);
182     }
183 
184     @Override
compareTo(final Message o)185     public int compareTo(final Message o) {
186         int i;
187         if (text != null && (i = text.compareTo(o.text)) != 0) {
188             return i;
189         }
190         if (duration != null && (i = duration.compareTo(o.duration)) != 0) {
191             return i;
192         }
193         return tags.size() - o.tags.size();
194     }
195 
196     static class MessageLevelSerializer extends StdSerializer<MessageLevel> {
197         private static final long serialVersionUID = 928540953227342817L;
198 
MessageLevelSerializer()199         MessageLevelSerializer() {
200             this(null);
201         }
202 
MessageLevelSerializer(Class<MessageLevel> vc)203         MessageLevelSerializer(Class<MessageLevel> vc) {
204             super(vc);
205         }
206 
207         @Override
serialize(final MessageLevel messageLevel, final JsonGenerator jsonGenerator, final SerializerProvider serializerProvider)208         public void serialize(final MessageLevel messageLevel,
209                                 final JsonGenerator jsonGenerator,
210                                 final SerializerProvider serializerProvider) throws IOException {
211             jsonGenerator.writeString(messageLevel.toString().toLowerCase(Locale.ROOT));
212         }
213     }
214 
215     private static class MessageLevelDeserializer extends StdDeserializer<MessageLevel> {
216         private static final long serialVersionUID = 928540953227342817L;
217 
MessageLevelDeserializer()218         MessageLevelDeserializer() {
219             this(null);
220         }
221 
MessageLevelDeserializer(Class<?> vc)222         MessageLevelDeserializer(Class<?> vc) {
223             super(vc);
224         }
225 
226         @Override
deserialize(final JsonParser parser, final DeserializationContext context)227         public MessageLevel deserialize(final JsonParser parser, final DeserializationContext context)
228                 throws IOException {
229             try {
230                 return MessageLevel.fromString(context.readValue(parser, String.class));
231             } catch (DateTimeParseException e) {
232                 throw new IOException(e);
233             }
234         }
235     }
236 
237     private static class DurationSerializer extends StdSerializer<Duration> {
238 
239         private static final long serialVersionUID = 5275434375701446542L;
240 
DurationSerializer()241         DurationSerializer() {
242             this(null);
243         }
244 
DurationSerializer(final Class<Duration> cl)245         DurationSerializer(final Class<Duration> cl) {
246             super(cl);
247         }
248 
249         @Override
serialize( final Duration duration, final JsonGenerator jsonGenerator, final SerializerProvider serializerProvider )250         public void serialize(
251                 final Duration duration,
252                 final JsonGenerator jsonGenerator,
253                 final SerializerProvider serializerProvider
254         ) throws IOException {
255             if (duration != null) {
256                 jsonGenerator.writeString(duration.toString());
257             } else {
258                 jsonGenerator.writeNull();
259             }
260         }
261     }
262 
263     private static class DurationDeserializer extends StdDeserializer<Duration> {
264         private static final long serialVersionUID = 5513386447457242809L;
265 
DurationDeserializer()266         DurationDeserializer() {
267             this(null);
268         }
269 
DurationDeserializer(Class<?> vc)270         DurationDeserializer(Class<?> vc) {
271             super(vc);
272         }
273 
274         @Override
deserialize(final JsonParser parser, final DeserializationContext context)275         public Duration deserialize(final JsonParser parser, final DeserializationContext context)
276                 throws IOException {
277             try {
278                 return Duration.parse(context.readValue(parser, String.class));
279             } catch (DateTimeParseException e) {
280                 throw new IOException(e);
281             }
282         }
283     }
284 
285     protected static class HTMLSerializer extends StdSerializer<String> {
286 
287         private static final long serialVersionUID = -2843900664165513923L;
288 
HTMLSerializer()289         HTMLSerializer() {
290             this(null);
291         }
292 
HTMLSerializer(final Class<String> cl)293         HTMLSerializer(final Class<String> cl) {
294             super(cl);
295         }
296 
297         @Override
serialize( final String string, final JsonGenerator jsonGenerator, final SerializerProvider serializerProvider )298         public void serialize(
299                 final String string,
300                 final JsonGenerator jsonGenerator,
301                 final SerializerProvider serializerProvider
302         ) throws IOException {
303             if (string != null) {
304                 jsonGenerator.writeString(Util.encode(string));
305             } else {
306                 jsonGenerator.writeNull();
307             }
308         }
309     }
310 }
311