extensions/net.sf.basedb.meludi/trunk/src/net/sf/basedb/meludi/dao/Patient.java

Code
Comments
Other
Rev Date Author Line
2933 14 Nov 14 olle 1 package net.sf.basedb.meludi.dao;
2933 14 Nov 14 olle 2
2933 14 Nov 14 olle 3
2933 14 Nov 14 olle 4 import java.text.SimpleDateFormat;
4913 13 Jul 18 olle 5 import java.util.ArrayList;
4913 13 Jul 18 olle 6 import java.util.Collection;
2933 14 Nov 14 olle 7 import java.util.List;
2933 14 Nov 14 olle 8 import java.util.concurrent.locks.ReentrantLock;
2933 14 Nov 14 olle 9
3385 10 Jun 15 olle 10 import net.sf.basedb.core.AnnotationSimpleRestriction;
2933 14 Nov 14 olle 11 import net.sf.basedb.core.AnnotationType;
2933 14 Nov 14 olle 12 import net.sf.basedb.core.BioSource;
2933 14 Nov 14 olle 13 import net.sf.basedb.core.DbControl;
2933 14 Nov 14 olle 14 import net.sf.basedb.core.Include;
2933 14 Nov 14 olle 15 import net.sf.basedb.core.InvalidDataException;
2933 14 Nov 14 olle 16 import net.sf.basedb.core.Item;
2933 14 Nov 14 olle 17 import net.sf.basedb.core.ItemQuery;
3385 10 Jun 15 olle 18 import net.sf.basedb.core.Operator;
2933 14 Nov 14 olle 19 import net.sf.basedb.core.Sample;
4913 13 Jul 18 olle 20 import net.sf.basedb.core.Type;
2933 14 Nov 14 olle 21 import net.sf.basedb.core.User;
2933 14 Nov 14 olle 22 import net.sf.basedb.core.query.Annotations;
2933 14 Nov 14 olle 23 import net.sf.basedb.core.query.Expressions;
2933 14 Nov 14 olle 24 import net.sf.basedb.core.query.Hql;
2933 14 Nov 14 olle 25 import net.sf.basedb.core.query.Orders;
2933 14 Nov 14 olle 26 import net.sf.basedb.core.query.Restrictions;
2933 14 Nov 14 olle 27 import net.sf.basedb.meludi.LockUtil;
2933 14 Nov 14 olle 28 import net.sf.basedb.meludi.Meludi;
2933 14 Nov 14 olle 29 import net.sf.basedb.meludi.ReservedItems;
2933 14 Nov 14 olle 30 import net.sf.basedb.meludi.converter.DateToStringConverter;
4796 08 May 18 olle 31 import net.sf.basedb.meludi.dao.Blood;
2933 14 Nov 14 olle 32 import net.sf.basedb.util.MD5;
2933 14 Nov 14 olle 33
2933 14 Nov 14 olle 34 /**
2933 14 Nov 14 olle 35   Class for loading information related to a patient.
2933 14 Nov 14 olle 36   
2933 14 Nov 14 olle 37   @author nicklas
2933 14 Nov 14 olle 38   @since 1.2
2933 14 Nov 14 olle 39 */
2933 14 Nov 14 olle 40 public class Patient
2933 14 Nov 14 olle 41   extends MeludiItem<BioSource>
2933 14 Nov 14 olle 42 {
2933 14 Nov 14 olle 43
2933 14 Nov 14 olle 44   /**
2933 14 Nov 14 olle 45     Find a patient by personal number. If exactly one match is found all is
2933 14 Nov 14 olle 46     good and this is the patient we are looking for. More than one match
2933 14 Nov 14 olle 47     is an error condition. No match indicates a patient that has not yet
2933 14 Nov 14 olle 48     been registered (null is returned).
2933 14 Nov 14 olle 49     
2933 14 Nov 14 olle 50     @param dc DbControl The DbControl to use.
2933 14 Nov 14 olle 51     @param pnr String The personal number.
2933 14 Nov 14 olle 52     @return Patient The Patient item found for the personal number.
2933 14 Nov 14 olle 53   */
2933 14 Nov 14 olle 54   public static Patient findByPersonalNumber(DbControl dc, String pnr)
2933 14 Nov 14 olle 55   {
2933 14 Nov 14 olle 56     Patient patient = null;
2933 14 Nov 14 olle 57     
2933 14 Nov 14 olle 58     AnnotationType pnrType = Annotationtype.PERSONAL_NUMBER.load(dc);
2933 14 Nov 14 olle 59     ItemQuery<BioSource> patientQuery = BioSource.getQuery();
2933 14 Nov 14 olle 60     Subtype.PATIENT.addFilter(dc, patientQuery);
3385 10 Jun 15 olle 61     patientQuery.restrict(new AnnotationSimpleRestriction(null, pnrType, Operator.EQ, pnr, true, false));
4223 09 Nov 16 olle 62     patientQuery.setIncludes(Meludi.INCLUDE_IN_CURRENT_PROJECT);
2933 14 Nov 14 olle 63     List<BioSource> patients = patientQuery.list(dc);
2933 14 Nov 14 olle 64     
2933 14 Nov 14 olle 65     if (patients.size() > 1)
2933 14 Nov 14 olle 66     {
2933 14 Nov 14 olle 67       throw new InvalidDataException(
2933 14 Nov 14 olle 68         "More than one patient with the personal number (" + pnr + ") was found. " +
2933 14 Nov 14 olle 69         "This wizard can't be used until that is corrected.");
2933 14 Nov 14 olle 70     }
2933 14 Nov 14 olle 71     if (patients.size() == 1)
2933 14 Nov 14 olle 72     {
2933 14 Nov 14 olle 73       patient = new Patient(patients.get(0));
2933 14 Nov 14 olle 74       patient.setAnnotation("personalNumber", pnr);
2933 14 Nov 14 olle 75     }
2933 14 Nov 14 olle 76     return patient;
2933 14 Nov 14 olle 77   }
2933 14 Nov 14 olle 78   
2933 14 Nov 14 olle 79   /**
2933 14 Nov 14 olle 80     Find a patient by personal number. If exactly one match is found all is
2933 14 Nov 14 olle 81     good and this is the patient we are looking for. More than one match
2933 14 Nov 14 olle 82     is an error condition. No match indicates a patient that has not yet
2933 14 Nov 14 olle 83     been registered (null is returned).
2933 14 Nov 14 olle 84     
2933 14 Nov 14 olle 85     @param dc DbControl The DbControl to use.
2933 14 Nov 14 olle 86     @param pnr String The personal number.
2933 14 Nov 14 olle 87     @param restrictToPatientBioSources boolean Flag indicating if search should be restricted to Patient BioSources.
2933 14 Nov 14 olle 88     @return Patient The Patient item found for the personal number.
2933 14 Nov 14 olle 89   */
2933 14 Nov 14 olle 90   public static Patient findByPersonalNumber(DbControl dc, String pnr, boolean restrictToPatientBioSources)
2933 14 Nov 14 olle 91   {
2933 14 Nov 14 olle 92     Patient patient = null;
2933 14 Nov 14 olle 93   
2933 14 Nov 14 olle 94     AnnotationType pnrType = Annotationtype.PERSONAL_NUMBER.load(dc);
2933 14 Nov 14 olle 95     ItemQuery<BioSource> patientQuery = BioSource.getQuery();
2933 14 Nov 14 olle 96     if (restrictToPatientBioSources)
2933 14 Nov 14 olle 97     {
2933 14 Nov 14 olle 98       Subtype.PATIENT.addFilter(dc, patientQuery);
2933 14 Nov 14 olle 99     }
3385 10 Jun 15 olle 100     patientQuery.restrict(new AnnotationSimpleRestriction(null, pnrType, Operator.EQ, pnr, true, false));
2933 14 Nov 14 olle 101     List<BioSource> patients = patientQuery.list(dc);
2933 14 Nov 14 olle 102   
2933 14 Nov 14 olle 103     if (patients.size() > 1)
2933 14 Nov 14 olle 104     {
2933 14 Nov 14 olle 105       throw new InvalidDataException(
2933 14 Nov 14 olle 106         "More than one patient with the personal number (" + pnr + ") was found. " +
2933 14 Nov 14 olle 107         "This wizard can't be used until that is corrected.");
2933 14 Nov 14 olle 108     }
2933 14 Nov 14 olle 109     if (patients.size() == 1)
2933 14 Nov 14 olle 110     {
2933 14 Nov 14 olle 111       patient = new Patient(patients.get(0));
2933 14 Nov 14 olle 112       patient.setAnnotation("personalNumber", pnr);
2933 14 Nov 14 olle 113     }
2933 14 Nov 14 olle 114     return patient;
2933 14 Nov 14 olle 115   }
2933 14 Nov 14 olle 116
2933 14 Nov 14 olle 117   /**
2933 14 Nov 14 olle 118     Find the patient the case is linked with. Null is returned if
2933 14 Nov 14 olle 119     not found.
2933 14 Nov 14 olle 120   */
2933 14 Nov 14 olle 121   public static Patient findByCase(DbControl dc, Case theCase)
2933 14 Nov 14 olle 122   {
2933 14 Nov 14 olle 123     Patient patient = null;
5325 05 Mar 19 olle 124     if (theCase != null)
2933 14 Nov 14 olle 125     {
5325 05 Mar 19 olle 126       Sample s = theCase.getSample();
5325 05 Mar 19 olle 127       BioSource b = s.getParentType() == Item.BIOSOURCE ? (BioSource)s.getParent() : null;
5325 05 Mar 19 olle 128       if (b != null)
5325 05 Mar 19 olle 129       {
5325 05 Mar 19 olle 130         patient = new Patient(b);
5325 05 Mar 19 olle 131       }      
2933 14 Nov 14 olle 132     }
2933 14 Nov 14 olle 133     return patient;
2933 14 Nov 14 olle 134   }
2933 14 Nov 14 olle 135   
4796 08 May 18 olle 136   /**
4796 08 May 18 olle 137     Find the patient the blood case is linked with. Null is returned if
4796 08 May 18 olle 138     not found.
4796 08 May 18 olle 139     @since 1.6
4796 08 May 18 olle 140   */
4796 08 May 18 olle 141   public static Patient findByBlood(DbControl dc, Blood blood)
4796 08 May 18 olle 142   {
4796 08 May 18 olle 143     Patient patient = null;
4796 08 May 18 olle 144     Sample s = blood.getSample();
4796 08 May 18 olle 145     BioSource b = s.getParentType() == Item.BIOSOURCE ? (BioSource)s.getParent() : null;
4796 08 May 18 olle 146     if (b != null)
4796 08 May 18 olle 147     {
4796 08 May 18 olle 148       patient = new Patient(b);
4796 08 May 18 olle 149     }
4796 08 May 18 olle 150     return patient;
4796 08 May 18 olle 151   }
4796 08 May 18 olle 152
4913 13 Jul 18 olle 153   public static Patient getById(DbControl dc, int patientId)
4913 13 Jul 18 olle 154   {
4913 13 Jul 18 olle 155     BioSource bs = BioSource.getById(dc, patientId);
4913 13 Jul 18 olle 156     return bs == null ? null : new Patient(bs);
4913 13 Jul 18 olle 157   }
4913 13 Jul 18 olle 158
4913 13 Jul 18 olle 159   public static List<Patient> toPatient(Collection<BioSource> biosources)
4913 13 Jul 18 olle 160   {
4913 13 Jul 18 olle 161     List<Patient> patients = new ArrayList<Patient>(biosources.size());
4913 13 Jul 18 olle 162     for (BioSource bs : biosources)
4913 13 Jul 18 olle 163     {
4913 13 Jul 18 olle 164       patients.add(new Patient(bs));
4913 13 Jul 18 olle 165     }
4913 13 Jul 18 olle 166     return patients;
4913 13 Jul 18 olle 167   }
4913 13 Jul 18 olle 168
4913 13 Jul 18 olle 169   /**
4913 13 Jul 18 olle 170     Find a patient by name. This method will check for an exact match
4913 13 Jul 18 olle 171     on the name of a patient. If exactly one match is found this is the
4913 13 Jul 18 olle 172     (primary) case. More than one match is an error condition. No match 
4913 13 Jul 18 olle 173     indicates a patient that has not yet been registered (null is returned).
4913 13 Jul 18 olle 174   */
4913 13 Jul 18 olle 175   public static Patient findByName(DbControl dc, String name)
4913 13 Jul 18 olle 176   {
4913 13 Jul 18 olle 177     Patient patient = null;
4913 13 Jul 18 olle 178   
4913 13 Jul 18 olle 179     // Look for a patient with the given name
4913 13 Jul 18 olle 180     ItemQuery<BioSource> patientQuery = BioSource.getQuery();
4913 13 Jul 18 olle 181     Subtype.PATIENT.addFilter(dc, patientQuery);
4913 13 Jul 18 olle 182     patientQuery.restrict(Restrictions.eq(Hql.property("name"), Expressions.parameter("name", name, Type.STRING)));
4913 13 Jul 18 olle 183     patientQuery.order(Orders.desc(Hql.property("name")));
4913 13 Jul 18 olle 184     patientQuery.include(Include.IN_PROJECT);
4913 13 Jul 18 olle 185     List<BioSource> patients = patientQuery.list(dc);
4913 13 Jul 18 olle 186  
4913 13 Jul 18 olle 187     // ...if more than one is found, something is incorrectly registered... abort
4913 13 Jul 18 olle 188     if (patients.size() > 1)
4913 13 Jul 18 olle 189     {
4913 13 Jul 18 olle 190       throw new InvalidDataException(
4913 13 Jul 18 olle 191       "Found " + patients.size() + " patients with the same name (" + name + 
4913 13 Jul 18 olle 192       "). This wizard can't be used until that is corrected.");
4913 13 Jul 18 olle 193     }
4913 13 Jul 18 olle 194
4913 13 Jul 18 olle 195     if (patients.size() == 1)
4913 13 Jul 18 olle 196     {
4913 13 Jul 18 olle 197       patient = new Patient((BioSource)patients.get(0));
4913 13 Jul 18 olle 198     }
4913 13 Jul 18 olle 199   
4913 13 Jul 18 olle 200     return patient;
4913 13 Jul 18 olle 201   }
4913 13 Jul 18 olle 202
2933 14 Nov 14 olle 203   // Reserve patient number for 5 minutes
2933 14 Nov 14 olle 204   private static final ReservedItems<Integer> RESERVED_PATIENT_NUMBERS = new ReservedItems<Integer>(300);
2933 14 Nov 14 olle 205   
2933 14 Nov 14 olle 206   /**
2933 14 Nov 14 olle 207     Generate the next auto-generated patient name. This method will search all patients
2933 14 Nov 14 olle 208     starting with the given prefix and find the one with the highest numeric suffix. 
2933 14 Nov 14 olle 209     The returned name is the found patient + 1.
2933 14 Nov 14 olle 210     @since 2.1
2933 14 Nov 14 olle 211   */
2933 14 Nov 14 olle 212   public static String generateNextName(DbControl dc, String prefix, Subtype subtype)
2933 14 Nov 14 olle 213   {
2933 14 Nov 14 olle 214     ItemQuery<BioSource> patientQuery = BioSource.getQuery();
2933 14 Nov 14 olle 215     if (subtype != null) subtype.addFilter(dc, patientQuery);
2933 14 Nov 14 olle 216     patientQuery.restrict(Restrictions.rlike(Hql.property("name"), Expressions.string("^" + prefix + "[0-9]+$")));
2933 14 Nov 14 olle 217     patientQuery.order(Orders.desc(Hql.property("name")));
2933 14 Nov 14 olle 218     patientQuery.setMaxResults(1);
2933 14 Nov 14 olle 219     patientQuery.include(Include.ALL);
2933 14 Nov 14 olle 220     List<BioSource> patients = patientQuery.list(dc);
2933 14 Nov 14 olle 221     
2933 14 Nov 14 olle 222     int nextPatientNumber = 1;
2933 14 Nov 14 olle 223     if (patients.size() > 0)
2933 14 Nov 14 olle 224     {
2933 14 Nov 14 olle 225       String patientName = patients.get(0).getName().substring(prefix.length());
2933 14 Nov 14 olle 226       nextPatientNumber = Integer.parseInt(patientName) + 1;
2933 14 Nov 14 olle 227     }
2933 14 Nov 14 olle 228     int maxPatientNumber = 100+nextPatientNumber;
2933 14 Nov 14 olle 229     while (!RESERVED_PATIENT_NUMBERS.reserve(nextPatientNumber))
2933 14 Nov 14 olle 230     {
2933 14 Nov 14 olle 231       nextPatientNumber++;
2933 14 Nov 14 olle 232       if (nextPatientNumber == maxPatientNumber)
2933 14 Nov 14 olle 233       {
2933 14 Nov 14 olle 234         throw new RuntimeException("Failed to generate a patient code after 100 tries");
2933 14 Nov 14 olle 235       }
2933 14 Nov 14 olle 236     }
4224 09 Nov 16 olle 237     Integer patientItemNumDigits = Meludi.fetchPatientItemNumDigits(dc.getSessionControl().getActiveProjectId());
4224 09 Nov 16 olle 238     String name = prefix + MD5.leftPad(Integer.toString(nextPatientNumber), '0', patientItemNumDigits);
2933 14 Nov 14 olle 239     return name;
2933 14 Nov 14 olle 240   }
2933 14 Nov 14 olle 241   
2933 14 Nov 14 olle 242   // Lock to prevent multiple patients with same name/personal from being registered
2933 14 Nov 14 olle 243   private static final ReentrantLock PATIENT_CHECK_LOCK = new ReentrantLock();
2933 14 Nov 14 olle 244   
2933 14 Nov 14 olle 245   /**
2933 14 Nov 14 olle 246     Check if a patient with the given personal number or name already exists
2933 14 Nov 14 olle 247     or not in the database. If the patient exists, an exception is thrown.
2933 14 Nov 14 olle 248     The first thread that calls this metod will block other treads from calling
2933 14 Nov 14 olle 249     it before the transaction is completed.
2933 14 Nov 14 olle 250     @since 2.16
2933 14 Nov 14 olle 251   */
2933 14 Nov 14 olle 252   public static void ensureNotExistingPatient(DbControl dc, String pnr, String name)
2933 14 Nov 14 olle 253   {
2933 14 Nov 14 olle 254     if (!LockUtil.acquireLockForTransaction(dc, PATIENT_CHECK_LOCK))
2933 14 Nov 14 olle 255     {
2933 14 Nov 14 olle 256       throw new RuntimeException("Could not aquire exclusive lock on patient database");
2933 14 Nov 14 olle 257     }
2933 14 Nov 14 olle 258     
2933 14 Nov 14 olle 259     ItemQuery<BioSource> query = BioSource.getQuery();
2933 14 Nov 14 olle 260     Subtype.PATIENT.addFilter(dc, query);
4223 09 Nov 16 olle 261     query.setIncludes(Meludi.INCLUDE_IN_CURRENT_PROJECT);
2933 14 Nov 14 olle 262     
2933 14 Nov 14 olle 263     query.join(Annotations.innerJoin(Annotationtype.PERSONAL_NUMBER.get(dc), "pn"));
2933 14 Nov 14 olle 264     query.restrict(Restrictions.or(
2933 14 Nov 14 olle 265       Restrictions.eq(Hql.property("name"), Expressions.string(name)),
2933 14 Nov 14 olle 266       Restrictions.eq(Hql.alias("pn"), Expressions.string(pnr))
2933 14 Nov 14 olle 267     ));
2933 14 Nov 14 olle 268     
2933 14 Nov 14 olle 269     List<BioSource> patients = query.list(dc);
2933 14 Nov 14 olle 270     if (patients.size() == 0) return;
2933 14 Nov 14 olle 271     
2933 14 Nov 14 olle 272     BioSource patient = patients.get(0);
2933 14 Nov 14 olle 273     User owner = patient.getOwner();
2933 14 Nov 14 olle 274     
2933 14 Nov 14 olle 275     throw new RuntimeException("Patient '" + (patient.getName().equals(name) ? name : pnr) + 
2933 14 Nov 14 olle 276         "' has already been registered by " + owner.getName());
2933 14 Nov 14 olle 277   }
2933 14 Nov 14 olle 278   
2933 14 Nov 14 olle 279   private Patient(BioSource bioSource)
2933 14 Nov 14 olle 280   {
2933 14 Nov 14 olle 281     super(bioSource);
2933 14 Nov 14 olle 282   }
2933 14 Nov 14 olle 283   
2933 14 Nov 14 olle 284   /**
2933 14 Nov 14 olle 285     Get the real biosource that represents this patient in BASE.
2933 14 Nov 14 olle 286   */
2933 14 Nov 14 olle 287   public BioSource getBioSource()
2933 14 Nov 14 olle 288   {
2933 14 Nov 14 olle 289     return getItem();
2933 14 Nov 14 olle 290   }
2933 14 Nov 14 olle 291   
2933 14 Nov 14 olle 292   /**
2933 14 Nov 14 olle 293     Load a default set of annotations for the patient.
2933 14 Nov 14 olle 294     <ul>
2933 14 Nov 14 olle 295     <li>{@link Annotationtype#PERSONAL_NUMBER}
2933 14 Nov 14 olle 296     <li>{@link Annotationtype#FAMILY_NAME}
2933 14 Nov 14 olle 297     <li>{@link Annotationtype#ALL_FIRST_NAMES}
2933 14 Nov 14 olle 298     <li>{@link Annotationtype#GENDER}
2933 14 Nov 14 olle 299     <li>{@link Annotationtype#DATE_OF_BIRTH}
2933 14 Nov 14 olle 300     </ul>
2933 14 Nov 14 olle 301   */
2933 14 Nov 14 olle 302   public void loadDefaultAnnotations(DbControl dc)
2933 14 Nov 14 olle 303   {
2933 14 Nov 14 olle 304     loadAnnotations(dc, "personalNumber", Annotationtype.PERSONAL_NUMBER, null);
2933 14 Nov 14 olle 305     loadAnnotations(dc, "familyName", Annotationtype.FAMILY_NAME, null);
2933 14 Nov 14 olle 306     loadAnnotations(dc, "allFirstNames", Annotationtype.ALL_FIRST_NAMES, null);
2933 14 Nov 14 olle 307     loadAnnotations(dc, "gender", Annotationtype.GENDER, null);
2933 14 Nov 14 olle 308     loadAnnotations(dc, "dateOfBirth", Annotationtype.DATE_OF_BIRTH, new DateToStringConverter(new SimpleDateFormat("yyyy-MM-dd")));
2933 14 Nov 14 olle 309   }
2933 14 Nov 14 olle 310
2933 14 Nov 14 olle 311 }