extensions/net.sf.basedb.reggie/trunk/src/net/sf/basedb/reggie/plugins/cmd/CopyFromSample.java

Code
Comments
Other
Rev Date Author Line
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 18   A validator implementation that is backed by a pre-registered
6581 10 Feb 22 nicklas 19   Sample (Specimen or NoSpecimen) item. Values that are missing in 
6581 10 Feb 22 nicklas 20   the JSON file can instead be picked from the Sample item. If 
6581 10 Feb 22 nicklas 21   there are values in both places it will also verify that they are 
6581 10 Feb 22 nicklas 22   not different.
6572 07 Feb 22 nicklas 23   
6581 10 Feb 22 nicklas 24   Start by creating an instance of CopyFromSample and then use one of the
6581 10 Feb 22 nicklas 25   copy*() methods. It is valid to use a null sample. In this case the
6581 10 Feb 22 nicklas 26   parent validator will be returned.
6581 10 Feb 22 nicklas 27   
6572 07 Feb 22 nicklas 28   @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 36     Create a new factory that uses the given sample for copying values.
6581 10 Feb 22 nicklas 37     If the sample is null, all copy*() methods will return the parent 
6581 10 Feb 22 nicklas 38     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 46     Create a validator that copies the given annotation type from the sample
6581 10 Feb 22 nicklas 47     if no value is found in the JSON file. If there is a value in both the JSON
6581 10 Feb 22 nicklas 48     and sample this will be reported as an error or warning (and the value from the
6581 10 Feb 22 nicklas 49     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 109         // 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 116         // 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 127         // We have values from both the JSON file and Sample item
6976 16 Jan 23 nicklas 128         // 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 132           // Check if either of timestamps is equal to the other if truncated to midnight
6976 16 Jan 23 nicklas 133           // This will handle the case when both timestamps have the same date but
6976 16 Jan 23 nicklas 134           // 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 144           // Special case for PAD where we also check the AlternatePAD annotation and
6992 19 Jan 23 nicklas 145           // 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 154                 //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 161               // In this case we return the value from the sample
6992 19 Jan 23 nicklas 162               //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 182     Defines if a message should be reported as a warning, error or not at all.
6975 13 Jan 23 nicklas 183     @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