6572 |
07 Feb 22 |
nicklas |
1 |
package net.sf.basedb.reggie.plugins.cmd; |
6572 |
07 Feb 22 |
nicklas |
2 |
|
6976 |
16 Jan 23 |
nicklas |
3 |
import java.time.Instant; |
6976 |
16 Jan 23 |
nicklas |
4 |
import java.time.LocalDateTime; |
6976 |
16 Jan 23 |
nicklas |
5 |
import java.time.ZoneId; |
6976 |
16 Jan 23 |
nicklas |
6 |
import java.time.temporal.ChronoUnit; |
6976 |
16 Jan 23 |
nicklas |
7 |
import java.util.Date; |
6976 |
16 Jan 23 |
nicklas |
8 |
|
6572 |
07 Feb 22 |
nicklas |
9 |
import net.sf.basedb.clients.web.formatter.FormatterFactory; |
6572 |
07 Feb 22 |
nicklas |
10 |
import net.sf.basedb.core.DbControl; |
6581 |
10 Feb 22 |
nicklas |
11 |
import net.sf.basedb.core.Sample; |
6975 |
13 Jan 23 |
nicklas |
12 |
import net.sf.basedb.core.Type; |
6976 |
16 Jan 23 |
nicklas |
13 |
import net.sf.basedb.reggie.Reggie; |
6572 |
07 Feb 22 |
nicklas |
14 |
import net.sf.basedb.reggie.dao.Annotationtype; |
6572 |
07 Feb 22 |
nicklas |
15 |
import net.sf.basedb.util.formatter.Formatter; |
6572 |
07 Feb 22 |
nicklas |
16 |
|
6572 |
07 Feb 22 |
nicklas |
17 |
/** |
6572 |
07 Feb 22 |
nicklas |
A validator implementation that is backed by a pre-registered |
6581 |
10 Feb 22 |
nicklas |
Sample (Specimen or NoSpecimen) item. Values that are missing in |
6581 |
10 Feb 22 |
nicklas |
the JSON file can instead be picked from the Sample item. If |
6581 |
10 Feb 22 |
nicklas |
there are values in both places it will also verify that they are |
6581 |
10 Feb 22 |
nicklas |
not different. |
6572 |
07 Feb 22 |
nicklas |
23 |
|
6581 |
10 Feb 22 |
nicklas |
Start by creating an instance of CopyFromSample and then use one of the |
6581 |
10 Feb 22 |
nicklas |
copy*() methods. It is valid to use a null sample. In this case the |
6581 |
10 Feb 22 |
nicklas |
parent validator will be returned. |
6581 |
10 Feb 22 |
nicklas |
27 |
|
6572 |
07 Feb 22 |
nicklas |
@since 4.36 |
6572 |
07 Feb 22 |
nicklas |
29 |
*/ |
6581 |
10 Feb 22 |
nicklas |
30 |
public class CopyFromSample |
6572 |
07 Feb 22 |
nicklas |
31 |
{ |
6572 |
07 Feb 22 |
nicklas |
32 |
|
6581 |
10 Feb 22 |
nicklas |
33 |
private final Sample sample; |
6572 |
07 Feb 22 |
nicklas |
34 |
|
6581 |
10 Feb 22 |
nicklas |
35 |
/** |
6581 |
10 Feb 22 |
nicklas |
Create a new factory that uses the given sample for copying values. |
6581 |
10 Feb 22 |
nicklas |
If the sample is null, all copy*() methods will return the parent |
6581 |
10 Feb 22 |
nicklas |
validator. |
6581 |
10 Feb 22 |
nicklas |
39 |
*/ |
6581 |
10 Feb 22 |
nicklas |
40 |
public CopyFromSample(Sample sample) |
6572 |
07 Feb 22 |
nicklas |
41 |
{ |
6581 |
10 Feb 22 |
nicklas |
42 |
this.sample = sample; |
6572 |
07 Feb 22 |
nicklas |
43 |
} |
6572 |
07 Feb 22 |
nicklas |
44 |
|
6581 |
10 Feb 22 |
nicklas |
45 |
/** |
6581 |
10 Feb 22 |
nicklas |
Create a validator that copies the given annotation type from the sample |
6581 |
10 Feb 22 |
nicklas |
if no value is found in the JSON file. If there is a value in both the JSON |
6581 |
10 Feb 22 |
nicklas |
and sample this will be reported as an error or warning (and the value from the |
6581 |
10 Feb 22 |
nicklas |
JSON file is used). |
6581 |
10 Feb 22 |
nicklas |
50 |
*/ |
6975 |
13 Jan 23 |
nicklas |
51 |
public <F, T> ValueValidator<F, T> copyAnnotation(Annotationtype at, ValueValidator<F, T> parent, MessageLevel ifMissing, MessageLevel ifDiscordant, boolean warnOnCopy, boolean preferValueFromSample) |
6572 |
07 Feb 22 |
nicklas |
52 |
{ |
6975 |
13 Jan 23 |
nicklas |
53 |
return sample == null ? parent : new AnnotationValidator<>(sample, at, parent, ifMissing, ifDiscordant, warnOnCopy, preferValueFromSample); |
6572 |
07 Feb 22 |
nicklas |
54 |
} |
6581 |
10 Feb 22 |
nicklas |
55 |
|
6581 |
10 Feb 22 |
nicklas |
56 |
class AnnotationValidator<F, T> |
6581 |
10 Feb 22 |
nicklas |
57 |
implements ValueValidator<F, T> |
6581 |
10 Feb 22 |
nicklas |
58 |
{ |
6572 |
07 Feb 22 |
nicklas |
59 |
|
6581 |
10 Feb 22 |
nicklas |
60 |
private final Sample sample; |
6581 |
10 Feb 22 |
nicklas |
61 |
private final ValueValidator<F, T> parent; |
6581 |
10 Feb 22 |
nicklas |
62 |
private final Annotationtype at; |
6975 |
13 Jan 23 |
nicklas |
63 |
private final MessageLevel ifDiscordant; |
6975 |
13 Jan 23 |
nicklas |
64 |
private final MessageLevel ifMissing; |
6904 |
29 Nov 22 |
nicklas |
65 |
private final boolean warnOnCopy; |
6975 |
13 Jan 23 |
nicklas |
66 |
private final boolean preferValueFromSample; |
6581 |
10 Feb 22 |
nicklas |
67 |
|
6975 |
13 Jan 23 |
nicklas |
68 |
AnnotationValidator(Sample sample, Annotationtype at, ValueValidator<F, T> parent, MessageLevel ifMissing, MessageLevel ifDiscordant, |
6975 |
13 Jan 23 |
nicklas |
69 |
boolean warnOnCopy, boolean preferValueFromSample) |
6572 |
07 Feb 22 |
nicklas |
70 |
{ |
6581 |
10 Feb 22 |
nicklas |
71 |
this.sample = sample; |
6581 |
10 Feb 22 |
nicklas |
72 |
this.at = at; |
6581 |
10 Feb 22 |
nicklas |
73 |
this.parent = parent; |
6975 |
13 Jan 23 |
nicklas |
74 |
this.ifMissing = ifMissing; |
6975 |
13 Jan 23 |
nicklas |
75 |
this.ifDiscordant = ifDiscordant; |
6904 |
29 Nov 22 |
nicklas |
76 |
this.warnOnCopy = warnOnCopy; |
6975 |
13 Jan 23 |
nicklas |
77 |
this.preferValueFromSample = preferValueFromSample; |
6572 |
07 Feb 22 |
nicklas |
78 |
} |
6581 |
10 Feb 22 |
nicklas |
79 |
|
6581 |
10 Feb 22 |
nicklas |
80 |
@SuppressWarnings("unchecked") |
6581 |
10 Feb 22 |
nicklas |
81 |
@Override |
6581 |
10 Feb 22 |
nicklas |
82 |
public Class<F> getExpectedClass() |
6572 |
07 Feb 22 |
nicklas |
83 |
{ |
6581 |
10 Feb 22 |
nicklas |
84 |
return parent != null ? parent.getExpectedClass() : (Class<F>)at.getValueType().getValueClass(); |
6572 |
07 Feb 22 |
nicklas |
85 |
} |
6581 |
10 Feb 22 |
nicklas |
86 |
|
6581 |
10 Feb 22 |
nicklas |
87 |
@Override |
6581 |
10 Feb 22 |
nicklas |
88 |
@SuppressWarnings("unchecked") |
6581 |
10 Feb 22 |
nicklas |
89 |
public T isValid(DbControl dc, F value, JsonSection section, String entryKey) |
6572 |
07 Feb 22 |
nicklas |
90 |
{ |
6581 |
10 Feb 22 |
nicklas |
91 |
T valueFromSample = (T)at.getAnnotationValue(dc, sample); |
6975 |
13 Jan 23 |
nicklas |
92 |
Type valueType = at.getValueType(); |
6976 |
16 Jan 23 |
nicklas |
93 |
Formatter<T> formatter = null; |
6976 |
16 Jan 23 |
nicklas |
94 |
if (valueType == Type.TIMESTAMP) |
6976 |
16 Jan 23 |
nicklas |
95 |
{ |
6976 |
16 Jan 23 |
nicklas |
96 |
formatter = (Formatter<T>)Reggie.CONVERTER_DATETIME_TO_STRING_WITH_SEPARATOR; |
6976 |
16 Jan 23 |
nicklas |
97 |
} |
6976 |
16 Jan 23 |
nicklas |
98 |
else if (valueType == Type.DATE) |
6976 |
16 Jan 23 |
nicklas |
99 |
{ |
6976 |
16 Jan 23 |
nicklas |
100 |
formatter = (Formatter<T>)Reggie.CONVERTER_DATE_TO_STRING_WITH_SEPARATOR; |
6976 |
16 Jan 23 |
nicklas |
101 |
} |
6976 |
16 Jan 23 |
nicklas |
102 |
else |
6976 |
16 Jan 23 |
nicklas |
103 |
{ |
6976 |
16 Jan 23 |
nicklas |
104 |
formatter = FormatterFactory.getTypeFormatter(dc.getSessionControl(), valueType); |
6976 |
16 Jan 23 |
nicklas |
105 |
} |
6581 |
10 Feb 22 |
nicklas |
106 |
|
6904 |
29 Nov 22 |
nicklas |
107 |
if (valueFromSample == null && value == null) |
6904 |
29 Nov 22 |
nicklas |
108 |
{ |
6975 |
13 Jan 23 |
nicklas |
// There is no value in the JSON file and no value in the sample |
6975 |
13 Jan 23 |
nicklas |
110 |
ifMissing.addMessage(section, "Missing value in JSON and "+sample.getName()+": " + entryKey); |
6904 |
29 Nov 22 |
nicklas |
111 |
return null; |
6904 |
29 Nov 22 |
nicklas |
112 |
} |
6904 |
29 Nov 22 |
nicklas |
113 |
|
6581 |
10 Feb 22 |
nicklas |
114 |
if (valueFromSample != null && value == null) |
6572 |
07 Feb 22 |
nicklas |
115 |
{ |
6581 |
10 Feb 22 |
nicklas |
// There is no value in the JSON file but we use what we have from the Sample item |
6904 |
29 Nov 22 |
nicklas |
117 |
if (warnOnCopy) |
6904 |
29 Nov 22 |
nicklas |
118 |
{ |
6904 |
29 Nov 22 |
nicklas |
119 |
section.addWarningMessage(entryKey + " is copied: " + formatter.format(valueFromSample)); |
6904 |
29 Nov 22 |
nicklas |
120 |
} |
6581 |
10 Feb 22 |
nicklas |
121 |
return valueFromSample; |
6572 |
07 Feb 22 |
nicklas |
122 |
} |
6581 |
10 Feb 22 |
nicklas |
123 |
|
6581 |
10 Feb 22 |
nicklas |
124 |
T valueFromJSON = parent != null ? parent.isValid(dc, value, section, entryKey) : (T)value; |
6581 |
10 Feb 22 |
nicklas |
125 |
if (valueFromJSON != null && valueFromSample != null) |
6581 |
10 Feb 22 |
nicklas |
126 |
{ |
6581 |
10 Feb 22 |
nicklas |
// We have values from both the JSON file and Sample item |
6976 |
16 Jan 23 |
nicklas |
// If they don't agree we report it as an error or warning |
6976 |
16 Jan 23 |
nicklas |
129 |
boolean valuesAreDifferent = !valueFromJSON.equals(valueFromSample); |
6976 |
16 Jan 23 |
nicklas |
130 |
if (valuesAreDifferent && valueType == Type.TIMESTAMP) |
6581 |
10 Feb 22 |
nicklas |
131 |
{ |
6976 |
16 Jan 23 |
nicklas |
// Check if either of timestamps is equal to the other if truncated to midnight |
6976 |
16 Jan 23 |
nicklas |
// This will handle the case when both timestamps have the same date but |
6976 |
16 Jan 23 |
nicklas |
// one of them is missing a time part |
6976 |
16 Jan 23 |
nicklas |
135 |
LocalDateTime tsJSON = LocalDateTime.ofInstant(Instant.ofEpochMilli(((Date)valueFromJSON).getTime()), ZoneId.systemDefault()); |
6976 |
16 Jan 23 |
nicklas |
136 |
LocalDateTime tsSample = LocalDateTime.ofInstant(Instant.ofEpochMilli(((Date)valueFromSample).getTime()), ZoneId.systemDefault()); |
6976 |
16 Jan 23 |
nicklas |
137 |
LocalDateTime midnightJSON = tsJSON.truncatedTo(ChronoUnit.DAYS); |
6976 |
16 Jan 23 |
nicklas |
138 |
LocalDateTime midnightSample = tsSample.truncatedTo(ChronoUnit.DAYS); |
6976 |
16 Jan 23 |
nicklas |
139 |
valuesAreDifferent = !midnightSample.equals(tsJSON) && !midnightJSON.equals(tsSample); |
6976 |
16 Jan 23 |
nicklas |
140 |
} |
6976 |
16 Jan 23 |
nicklas |
141 |
|
6976 |
16 Jan 23 |
nicklas |
142 |
if (valuesAreDifferent) |
6976 |
16 Jan 23 |
nicklas |
143 |
{ |
6992 |
19 Jan 23 |
nicklas |
// Special case for PAD where we also check the AlternatePAD annotation and |
6992 |
19 Jan 23 |
nicklas |
// allow a suffix after '-' in the JSON file |
6925 |
02 Dec 22 |
nicklas |
146 |
if (at == Annotationtype.PAD) |
6925 |
02 Dec 22 |
nicklas |
147 |
{ |
6992 |
19 Jan 23 |
nicklas |
148 |
String padFromJSON = valueFromJSON.toString(); |
6992 |
19 Jan 23 |
nicklas |
149 |
String alternatePAD = (String)Annotationtype.ALTERNATE_PAD.getAnnotationValue(dc, sample); |
6992 |
19 Jan 23 |
nicklas |
150 |
if (alternatePAD != null) |
6925 |
02 Dec 22 |
nicklas |
151 |
{ |
6992 |
19 Jan 23 |
nicklas |
152 |
if (padFromJSON.equals(alternatePAD) || padFromJSON.startsWith(alternatePAD+"-")) |
6992 |
19 Jan 23 |
nicklas |
153 |
{ |
6992 |
19 Jan 23 |
nicklas |
//section.addWarningMessage("Specimen.PAD matched with AlternatePAD: "+valueFromJSON+" › "+alternatePAD); |
6992 |
19 Jan 23 |
nicklas |
155 |
return valueFromSample; |
6992 |
19 Jan 23 |
nicklas |
156 |
} |
6992 |
19 Jan 23 |
nicklas |
157 |
} |
6992 |
19 Jan 23 |
nicklas |
158 |
|
6992 |
19 Jan 23 |
nicklas |
159 |
if (padFromJSON.startsWith(valueFromSample.toString()+"-")) |
6992 |
19 Jan 23 |
nicklas |
160 |
{ |
6925 |
02 Dec 22 |
nicklas |
// In this case we return the value from the sample |
6992 |
19 Jan 23 |
nicklas |
//section.addWarningMessage("PAD changed: "+valueFromJSON+" › "+valueFromSample); |
6925 |
02 Dec 22 |
nicklas |
163 |
return valueFromSample; |
6925 |
02 Dec 22 |
nicklas |
164 |
} |
6925 |
02 Dec 22 |
nicklas |
165 |
} |
6925 |
02 Dec 22 |
nicklas |
166 |
|
6975 |
13 Jan 23 |
nicklas |
167 |
String msg = entryKey+" in JSON (" + value + ") != "+sample.getName()+"#"+at.getName()+" ("+formatter.format(valueFromSample)+"). " |
6975 |
13 Jan 23 |
nicklas |
168 |
+ "Using value from "+(preferValueFromSample ? "sample" : "JSON")+"."; |
6975 |
13 Jan 23 |
nicklas |
169 |
ifDiscordant.addMessage(section, msg); |
6975 |
13 Jan 23 |
nicklas |
170 |
if (ifDiscordant == MessageLevel.ERROR) |
6581 |
10 Feb 22 |
nicklas |
171 |
{ |
6581 |
10 Feb 22 |
nicklas |
172 |
return null; |
6581 |
10 Feb 22 |
nicklas |
173 |
} |
6581 |
10 Feb 22 |
nicklas |
174 |
} |
6581 |
10 Feb 22 |
nicklas |
175 |
} |
6581 |
10 Feb 22 |
nicklas |
176 |
|
6975 |
13 Jan 23 |
nicklas |
177 |
return preferValueFromSample && valueFromSample != null ? valueFromSample : valueFromJSON; |
6572 |
07 Feb 22 |
nicklas |
178 |
} |
6975 |
13 Jan 23 |
nicklas |
179 |
} |
6975 |
13 Jan 23 |
nicklas |
180 |
|
6975 |
13 Jan 23 |
nicklas |
181 |
/** |
6975 |
13 Jan 23 |
nicklas |
Defines if a message should be reported as a warning, error or not at all. |
6975 |
13 Jan 23 |
nicklas |
@since 4.42 |
6975 |
13 Jan 23 |
nicklas |
184 |
*/ |
6975 |
13 Jan 23 |
nicklas |
185 |
public enum MessageLevel |
6975 |
13 Jan 23 |
nicklas |
186 |
{ |
6975 |
13 Jan 23 |
nicklas |
187 |
NONE |
6975 |
13 Jan 23 |
nicklas |
188 |
{ |
6975 |
13 Jan 23 |
nicklas |
189 |
@Override |
6975 |
13 Jan 23 |
nicklas |
190 |
public void addMessage(JsonSection section, String msg) |
6975 |
13 Jan 23 |
nicklas |
191 |
{} |
6975 |
13 Jan 23 |
nicklas |
192 |
}, |
6581 |
10 Feb 22 |
nicklas |
193 |
|
6975 |
13 Jan 23 |
nicklas |
194 |
WARN |
6975 |
13 Jan 23 |
nicklas |
195 |
{ |
6975 |
13 Jan 23 |
nicklas |
196 |
@Override |
6975 |
13 Jan 23 |
nicklas |
197 |
public void addMessage(JsonSection section, String msg) |
6975 |
13 Jan 23 |
nicklas |
198 |
{ |
6975 |
13 Jan 23 |
nicklas |
199 |
section.addWarningMessage(msg); |
6975 |
13 Jan 23 |
nicklas |
200 |
} |
6975 |
13 Jan 23 |
nicklas |
201 |
}, |
6975 |
13 Jan 23 |
nicklas |
202 |
|
6975 |
13 Jan 23 |
nicklas |
203 |
ERROR |
6975 |
13 Jan 23 |
nicklas |
204 |
{ |
6975 |
13 Jan 23 |
nicklas |
205 |
@Override |
6975 |
13 Jan 23 |
nicklas |
206 |
public void addMessage(JsonSection section, String msg) |
6975 |
13 Jan 23 |
nicklas |
207 |
{ |
6975 |
13 Jan 23 |
nicklas |
208 |
section.addErrorMessage(msg); |
6975 |
13 Jan 23 |
nicklas |
209 |
} |
6975 |
13 Jan 23 |
nicklas |
210 |
}; |
6975 |
13 Jan 23 |
nicklas |
211 |
|
6975 |
13 Jan 23 |
nicklas |
212 |
public abstract void addMessage(JsonSection section, String msg); |
6572 |
07 Feb 22 |
nicklas |
213 |
} |
6581 |
10 Feb 22 |
nicklas |
214 |
|
6572 |
07 Feb 22 |
nicklas |
215 |
} |
6581 |
10 Feb 22 |
nicklas |
216 |
|