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