/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is part of dcm4che, an implementation of DICOM(TM) in * Java(TM), hosted at https://github.com/gunterze/dcm4che. * * The Initial Developer of the Original Code is * Agfa Healthcare. * Portions created by the Initial Developer are Copyright (C) 2011 * the Initial Developer. All Rights Reserved. * * Contributor(s): * See @authors listed below * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ package org.dcm4chee.archive.entity; import org.dcm4che3.data.Attributes; import org.dcm4che3.data.DatePrecision; import org.dcm4che3.data.Tag; import org.dcm4che3.soundex.FuzzyStr; import org.dcm4che3.util.StringUtils; import org.dcm4chee.archive.conf.AttributeFilter; import org.dcm4chee.storage.conf.Availability; import org.hibernate.annotations.OptimisticLock; import javax.persistence.*; import java.io.Serializable; import java.util.Calendar; import java.util.Collection; import java.util.Date; import java.util.GregorianCalendar; /** * @author Damien Evans <damien.daddy@gmail.com> * @author Justin Falk <jfalkmu@gmail.com> * @author Gunter Zeilinger <gunterze@gmail.com> * @author Michael Backhaus <michael.backhaus@agfa.com> */ @NamedQueries({ @NamedQuery( name=Instance.FIND_BY_SOP_INSTANCE_UID_EAGER, query="SELECT i FROM Instance i " + "JOIN FETCH i.series se " + "JOIN FETCH se.study st " + "JOIN FETCH st.patient p " + "JOIN FETCH i.attributesBlob " + "JOIN FETCH se.attributesBlob " + "JOIN FETCH st.attributesBlob " + "JOIN FETCH p.attributesBlob " + "LEFT JOIN FETCH p.patientName pn " + "LEFT JOIN FETCH st.referringPhysicianName rpn " + "LEFT JOIN FETCH se.performingPhysicianName ppn " + "WHERE i.sopInstanceUID = ?1"), @NamedQuery( name=Instance.FIND_BY_SOP_INSTANCE_UID_EAGER_MANY, query="SELECT i FROM Instance i " + "JOIN FETCH i.series se " + "JOIN FETCH se.study st " + "JOIN FETCH st.patient p " + "JOIN FETCH i.attributesBlob " + "JOIN FETCH se.attributesBlob " + "JOIN FETCH st.attributesBlob " + "JOIN FETCH p.attributesBlob " + "LEFT JOIN FETCH p.patientName pn " + "LEFT JOIN FETCH st.referringPhysicianName rpn " + "LEFT JOIN FETCH se.performingPhysicianName ppn " + "WHERE i.sopInstanceUID IN (:uids)"), @NamedQuery( name=Instance.FIND_BY_SERIES_INSTANCE_UID, query="SELECT i FROM Instance i " + "WHERE i.series.seriesInstanceUID = ?1")}) @Entity @Table(name = "instance") public class Instance implements Serializable { private static final long serialVersionUID = -6510894512195470408L; public static final String FIND_BY_SOP_INSTANCE_UID_EAGER = "Instance.findBySOPInstanceUID.eager"; public static final String FIND_BY_SOP_INSTANCE_UID_EAGER_MANY = "Instance.findBySOPInstanceUIDMany.eager"; public static final String FIND_BY_SERIES_INSTANCE_UID = "Instance.findBySeriesInstanceUID"; @Id @GeneratedValue(strategy=GenerationType.IDENTITY) @Column(name = "pk") private long pk; @Version @Column(name = "version") private long version; //@Basic(optional = false) @Column(name = "created_time", updatable = false) private Date createdTime; //@Basic(optional = false) @Column(name = "updated_time") private Date updatedTime; //@Basic(optional = false) @Column(name = "sop_iuid", updatable = false, unique = true) private String sopInstanceUID; //@Basic(optional = false) @Column(name = "sop_cuid", updatable = false) private String sopClassUID; //@Basic(optional = false) @Column(name = "inst_no") private String instanceNumber; //@Basic(optional = false) @Column(name = "content_datetime", nullable=true) private Date contentDateTime; //@Basic(optional = false) @Column(name = "sr_complete") private String completionFlag; //@Basic(optional = false) @Column(name = "sr_verified") private String verificationFlag; //@Basic(optional = false) @Column(name = "inst_custom1") private String instanceCustomAttribute1; //@Basic(optional = false) @Column(name = "inst_custom2") private String instanceCustomAttribute2; //@Basic(optional = false) @Column(name = "inst_custom3") private String instanceCustomAttribute3; @Column(name = "retrieve_aets") private String retrieveAETs; @OneToMany(mappedBy = "instance", cascade = CascadeType.ALL, orphanRemoval = true) private Collection<ExternalRetrieveLocation> externalRetrieveLocations; //@Basic(optional = false) @Column(name = "availability") private Availability availability; @OneToOne(fetch=FetchType.LAZY, cascade=CascadeType.ALL, orphanRemoval = true, optional = false) @JoinColumn(name = "dicomattrs_fk") private AttributesBlob attributesBlob; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "srcode_fk") private Code conceptNameCode; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "reject_code_fk") private Code rejectionNoteCode; @OneToMany(mappedBy = "instance", cascade = CascadeType.ALL, orphanRemoval = true) private Collection<VerifyingObserver> verifyingObservers; @OneToMany(mappedBy = "instance", cascade = CascadeType.ALL, orphanRemoval = true) private Collection<ContentItem> contentItems; @ManyToOne(optional = false) @JoinColumn(name = "series_fk") private Series series; @OptimisticLock(excluded = true) // updates of locations should NOT increment the Instance version @ManyToMany(mappedBy = "instances") private Collection<Location> locations; @Transient private Attributes cachedAttributes; @Override public String toString() { return "Instance[pk=" + pk + ", uid=" + sopInstanceUID + ", class=" + sopClassUID + ", no=" + instanceNumber + "]"; } @PrePersist public void onPrePersist() { Date now = new Date(); createdTime = now; updatedTime = now; // update the series, which also has an important side-effect: it will increase the version field of the series // (which will in turn also increase the version field of the study). // this is necessary to ensure correct calculation of derived fields for series/studies. series.setUpdatedTime(now); } @PreUpdate public void onPreUpdate() { Date now = new Date(); updatedTime = now; // update the series, which also has an important side-effect: it will increase the version field of the series // (which will in turn also increase the version field of the study). // this is necessary to ensure correct calculation of derived fields for series/studies. series.setUpdatedTime(now); } @PreRemove public void onPreRemove() { Date now = new Date(); // update the series, which also has an important side-effect: it will increase the version field of the series. // this is necessary to ensure correct calculation of derived fields for series/studies. series.setUpdatedTime(now); } public AttributesBlob getAttributesBlob() { return attributesBlob; } public Attributes getAttributes() throws BlobCorruptedException { return attributesBlob.getAttributes(); } public long getPk() { return pk; } public Date getCreatedTime() { return createdTime; } public Date getUpdatedTime() { return updatedTime; } public String getSopInstanceUID() { return sopInstanceUID; } public String getSopClassUID() { return sopClassUID; } public String getInstanceNumber() { return instanceNumber; } public void setContentDateTime(Date contentDateTime) { this.contentDateTime = contentDateTime; } public Date getContentDateTime() { return contentDateTime; } public String getCompletionFlag() { return completionFlag; } public String getVerificationFlag() { return verificationFlag; } public String getInstanceCustomAttribute1() { return instanceCustomAttribute1; } public String getInstanceCustomAttribute2() { return instanceCustomAttribute2; } public String getInstanceCustomAttribute3() { return instanceCustomAttribute3; } public String[] getRetrieveAETs() { return StringUtils.split(retrieveAETs, '\\'); } public String getRawRetrieveAETs() { return retrieveAETs; } public void setRetrieveAETs(String... retrieveAETs) { this.retrieveAETs = StringUtils.concat(retrieveAETs, '\\'); } /** * Adds a retrieve AET. * @param aet AET to add * @return Returns {@code true} if the AET was added, returns {@code false} if the AET already * existed and therefore was not added. */ public boolean addRetrieveAET(String aet) { if(retrieveAETs == null) { setRetrieveAETs(aet); } String[] oldRetrieveAETs = getRetrieveAETs(); for(String retrieveAET : oldRetrieveAETs) { if(retrieveAET.equals(aet)) { return false; } } String[] newRetrieveAETs = new String[oldRetrieveAETs.length + 1]; System.arraycopy(oldRetrieveAETs, 0, newRetrieveAETs, 0, oldRetrieveAETs.length); newRetrieveAETs[oldRetrieveAETs.length] = aet; setRetrieveAETs(newRetrieveAETs); return true; } public String getEncodedRetrieveAETs() { return retrieveAETs; } public void setEncodedRetrieveAETs(String retrieveAETs) { this.retrieveAETs = retrieveAETs; } public String[] getAllRetrieveAETs() { return Utils.decodeAETs(retrieveAETs); } public Availability getAvailability() { return availability; } public void setAvailability(Availability availability) { this.availability = availability; } public Code getConceptNameCode() { return conceptNameCode; } public void setConceptNameCode(Code conceptNameCode) { this.conceptNameCode = conceptNameCode; } public Code getRejectionNoteCode() { return rejectionNoteCode; } public void setRejectionNoteCode(Code rejectionNoteCode) { this.rejectionNoteCode = rejectionNoteCode; } public Collection<VerifyingObserver> getVerifyingObservers() { return verifyingObservers; } public void setVerifyingObservers( Collection<VerifyingObserver> verifyingObservers) { this.verifyingObservers = verifyingObservers; } public Collection<ContentItem> getContentItems() { return contentItems; } public void setContentItems(Collection<ContentItem> contentItems) { this.contentItems = contentItems; } /** * @return Locations of instance. Note: This collection can NOT be used to update the locations of an instance, * as Location is the managing (owning) side of the relationship. Please use {@link Location#addInstance(Instance)} * instead. */ public Collection<Location> getLocations() { return locations; } public void setLocations(Collection<Location> locations) { this.locations = locations; } public Series getSeries() { return series; } public void setSeries(Series series) { this.series = series; } public long getVersion() { return version; } public void setVersion(long version) { this.version = version; } public Collection<ExternalRetrieveLocation> getExternalRetrieveLocations() { return externalRetrieveLocations; } public void setExternalRetrieveLocations( Collection<ExternalRetrieveLocation> externalRetrieveLocations) { this.externalRetrieveLocations = externalRetrieveLocations; } public void setAttributes(Attributes attrs, AttributeFilter filter, FuzzyStr fuzzyStr, String nullValue) { sopInstanceUID = attrs.getString(Tag.SOPInstanceUID); sopClassUID = attrs.getString(Tag.SOPClassUID); instanceNumber = attrs.getString(Tag.InstanceNumber, nullValue); Date dt = attrs.getDate(Tag.ContentDateAndTime, new DatePrecision(Calendar.SECOND)); if (dt != null) { Calendar adjustedDateTimeCal = new GregorianCalendar(); adjustedDateTimeCal.setTime(dt); adjustedDateTimeCal.set(Calendar.MILLISECOND, 0); contentDateTime = adjustedDateTimeCal.getTime(); } completionFlag = Utils.upper(attrs.getString(Tag.CompletionFlag, nullValue)); verificationFlag = Utils.upper(attrs.getString(Tag.VerificationFlag, nullValue)); instanceCustomAttribute1 = AttributeFilter.selectStringValue(attrs, filter.getCustomAttribute1(), nullValue); instanceCustomAttribute2 = AttributeFilter.selectStringValue(attrs, filter.getCustomAttribute2(), nullValue); instanceCustomAttribute3 = AttributeFilter.selectStringValue(attrs, filter.getCustomAttribute3(), nullValue); if (attributesBlob == null) attributesBlob = new AttributesBlob(new Attributes(attrs, filter.getCompleteSelection(attrs))); else attributesBlob.setAttributes(new Attributes(attrs, filter.getCompleteSelection(attrs))); } }