/* To change this template, choose Tools | Templates
 * and open the template in the editor. */
package lu.tudor.santec.gecamed.esante.ejb.entity.beans;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.text.DateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.persistence.Transient;

import lu.tudor.santec.gecamed.core.ejb.entity.beans.GECAMedEntityBean;
import lu.tudor.santec.gecamed.core.utils.GECAMedUtils;
import lu.tudor.santec.gecamed.esante.utils.exceptions.InvalidParameterException;
import lu.tudor.santec.i18n.Translatrix;

import org.apache.log4j.Logger;


/**
 * @author jens.ferring(at)tudor.lu
 * 
 * Based on infomed client sources.
 */
@NamedQueries(
{
	@NamedQuery(name = CdaDocument.LAST_UPDATE_OF_DSP,
			query = "SELECT o.downloadTime FROM CdaDocument o " +
					"WHERE dspId = :dspId " +
					"ORDER BY downloadTime DESC"),
	@NamedQuery(name = CdaDocument.GET_DOCUMENT_BY_OID,
			query = "SELECT OBJECT(o) FROM CdaDocument o " + 
					"WHERE documentUniqueId = :oid " + 
					"AND dspId = :dspId"),
	@NamedQuery(name = CdaDocument.GET_DOCUMENTS_OF_DSP,
			query = "SELECT OBJECT(o) FROM CdaDocument o " +
					"WHERE dspId = :dspId "),
//					"ORDER BY creationTime DESC"),
	@NamedQuery(name = CdaDocument.GET_DOCUMENT_COUNT,
			query = "SELECT COUNT(o.id) FROM CdaDocument o " +
					"WHERE documentUniqueId = :cdaUid " + 
					"AND dspId = :dspId"), 
	@NamedQuery(name = CdaDocument.GET_DOCUMENT_OF_ENTRY, 
			query = "SELECT OBJECT(o) FROM CdaDocument o " +
					"WHERE incidentEntryId = :entryId"),
	@NamedQuery(name = CdaDocument.GET_INCIDENT_ENTRY_BY_DOC_ID, 
			query = "SELECT OBJECT(e) FROM IncidentEntry e, Incident i, Dsp d " + 
					"WHERE e.cdaUniqueId = :uid " + 
					"AND e.incidentId = i.id " + 
					"AND d.id = :dspId " + 
					"AND i.patientId = d.patientId "),
	@NamedQuery(name = CdaDocument.GET_CDA_OIDS,
			query = "SELECT d.documentUniqueId FROM CdaDocument d " + 
					"WHERE d.dspId = :dspId"),
//	@NamedQuery(name = CdaDocument.GET_CDA_INCIDENT_ENTRIES_OF_PATIENT, 
//			query = "SELECT incidentEntryId FROM CdaDocument o " + 
//					"WHERE dspId = :patientId " + 
//					"AND incidentEntryId IS NOT NULL"),
//	@NamedQuery(name = CdaDocument.DELETE_DSP, 
//			query = "DELETE Dsp d WHERE d.id = :dspId"),
//	@NamedQuery(name = CdaDocument.DELETE_INCIDENT_ENTRIES_FROM_LIST, 
//			query = "DELETE IncidentEntry e " + 
//					"WHERE e.id IN (:entryIdList)"),
	@NamedQuery(name = CdaDocument.UPDATE_CDA_ERROR,
			query = "UPDATE CdaDocument " + 
					"SET error = :error " + 
					"WHERE id = :id"),
	@NamedQuery(name = CdaDocument.UPDATE_CDA_READ,
			query = "UPDATE CdaDocument " + 
					"SET read = :read " + 
					"WHERE id = :id"),
	@NamedQuery(name = CdaDocument.DELETE_CDA_ENTRIES_OF_PATIENT, 
			query = "DELETE IncidentEntry e " + 
					"WHERE e.entryTypeId = (" + 
					"  SELECT t.id FROM IncidentEntryType t " + 
					"  WHERE t.name = 'cda'" + 
					")" + 
					"AND e.incidentId IN (" + 
					"  SELECT i.id FROM Incident i " + 
					"  WHERE i.patientId = :patientId" + 
					")"),
	@NamedQuery(name = CdaDocument.COUNT_DOWNLOADED_NOT_INTEG_CDA_FILES,
			query = "SELECT COUNT(o.id) FROM CdaDocument o "
					+ "WHERE o.incidentEntryId IS NULL "
					+ "AND o.serverFileName IS NOT NULL"),
	@NamedQuery(name = CdaDocument.GET_DOWNLOADED_NOT_INTEG_CDA_FILES,
			query = "SELECT OBJECT(o) FROM CdaDocument o "
					+ "WHERE o.incidentEntryId IS NULL "
					+ "AND o.serverFileName IS NOT NULL")
})
@Entity
@Table (name = "cda", schema = "esante")
public class CdaDocument extends GECAMedEntityBean
{
	/* ======================================== */
	// CONSTANTS
	/* ======================================== */
	
	public static final String TIME_FORMAT	= "yyyyMMddHHmmss";
	
	private static final long	serialVersionUID	= 1L;
	
	private static final int REFLECTION_MODIFIER_TEST	= Modifier.FINAL | Modifier.STATIC | Modifier.TRANSIENT | Modifier.NATIVE;
	
	
	// Named queries
	public static final String LAST_UPDATE_OF_DSP					= "lastUpdateForPatient";
	public static final String GET_DOCUMENT_BY_OID					= "getDocumentByOid";
	public static final String GET_DOCUMENTS_OF_DSP					= "getDocumentsForPatient";
	public static final String GET_HISTORY_OF_DOCUMENT				= "getAllDocumentVersions";
	public static final String GET_DOCUMENT_COUNT					= "getCurrentDocumentVersion";
	public static final String GET_DOCUMENT_OF_ENTRY				= "getCurrentDocumentOfIncidentEntry";
	public static final String GET_INCIDENT_ENTRY_BY_DOC_ID			= "getIncidentEntryByDocId";
	public static final String GET_CDA_OIDS							= "getCdaOidList";
//	public static final String GET_CDA_INCIDENT_ENTRIES_OF_PATIENT	= "getCdaIncidentEntriesOfPatient";
//	public static final String UPDATE_PATIENT_LUXEMBOURG_ID			= "updatePatientLuxembourgId";
	public static final String UPDATE_CDA_ERROR						= "updateCdaError";
	public static final String UPDATE_CDA_READ						= "updateCdaRead";
//	public static final String DELETE_INCIDENT_ENTRIES_FROM_LIST	= "deleteIncidentEntriesFromList";
//	public static final String DELETE_DSP							= "deleteCdaDocumentsOfPatient";
	public static final String DELETE_CDA_ENTRIES_OF_PATIENT		= "deleteCdaEntriesOfPatient";
	public static final String COUNT_DOWNLOADED_NOT_INTEG_CDA_FILES	= "countDownloadedAndNotIntegratedCdaFiles";
	public static final String GET_DOWNLOADED_NOT_INTEG_CDA_FILES	= "getDownloadedAndNotIntegratedCdaFiles";
	
	
	// states
	public static final int STATUS_ERROR					= -1;
	public static final int	STATUS_NEW						= 0;
	public static final int	STATUS_UNREAD					= 1;
	public static final int	STATUS_READ						= 2;
	public static final int	STATUS_UNREAD_AND_DOWNLOADED	= 3;
	public static final int	STATUS_READ_AND_DOWNLOADED		= 4;
	public static final int	STATUS_UNREAD_AND_INTEGRATED	= 5;
	public static final int STATUS_READ_AND_INTEGRATED		= 6;
	public static final int	STATUS_UPLOADED					= 7;
	public static final int	MAX_STATUS						= 8;
	
	
	// confidentiality
	public static final String CONFIDENTIALITY_NORMAL		= "N";
	public static final String CONFIDENTIALITY_RESTRICTED	= "R";
	public static final String CONFIDENTIALITY_PRIVATE		= "I";
	public static final String CONFIDENTIALITY_BLOCKED		= "T";
	
	
	
	/**
	 * The name of the sub folder in the patient folder to store 
	 * the eSanté CDA documents.
	 */
	public static final String CDA_PREFIX			= "/CDA/";
	public static final String CDA_UPLOAD_PREFIX	= "/CDA/upload/";
	public static final String CDA_ERROR_PREFIX		= "error/";
	public static final String ESANTE_FOLDER		= "/server/default/log/esante/";
	
	public static final String XPATH_UID			= "*[local-name()='ExternalIdentifier' and @*[local-name()='identificationScheme']='urn:uuid:2e82c1f6-a085-4c72-9da3-8640a32e42ab']/@value";
	public static final String XPATH_OID			= "*[local-name()='ExternalIdentifier' and @*[local-name()='identificationScheme']='urn:uuid:2e82c1f6-a085-4c72-9da3-8640a32e42ab']/@registryObject";
	
	
	private static final String[]	BYTE_UNITS		= new String[] { " Bytes", " KB", " MB", " GB", " TB" };
	
	
	
	/* ======================================== */
	// MEMBERS
	/* ======================================== */
	
//	private static DateFormat	dateFormatter		= new SimpleDateFormat(TIME_FORMAT);
	
//	private static DateFormat	timeFormatter		= new SimpleDateFormat(TIME_FORMAT);
	
	/** the logger Object for this class */
	private static Logger logger = Logger.getLogger(CdaDocument.class.getName());
	
	
	/* ---------------------------------------- */
	// COLUMNS
	/* ---------------------------------------- */
	
	/**
	 * The GECAMed ID of the patient <br>
	 * <b>(this is NOT the eSanté patient ID)</b>
	 */
	private Integer		dspId;
	
	/**
	 * The ID of the incident entry, if this CDA is downloaded and integrated in
	 * GECAMed or <code>null</code> if it is not integrated in GECAMed and available
	 * only on the eSanté repository.
	 */
	private Integer		incidentEntryId;
	
	/**
	 * The time the first information of this document has been downloaded
	 */
	private Date		downloadTime;
	
	/**
	 * The ID of the incident entry this CDA was created from, if it was
	 * created from this GECAMed instance or <code>null</code> if it wasn't.
	 */
	private Integer		originIncidentEntryId;
	
	/**
	 * The name of the file in the GECAMed patient eSanté folder.
	 */
	private String		serverFileName;
	
	/**
	 * The status of this document (like new, unread, offline available
	 */
	private boolean		read;
	
	/**
	 * If something went wrong while downloading saving the document, 
	 * the error message is stored here.
	 */
	private String		errorMessage;
	
	/**
	 * The ID of the repository this document is located.
	 */
	private String		repositoryUniqueId;
	
	/**
	 * The ID of this document in the repository of the DSP.
	 */
	private String		documentUniqueId = null;
	
	/**
	 * The version of this document.
	 */
	private String		version;
	
	/**
	 * The title of this document.
	 */
	private String		title;
	
	private Dsp			dsp;
	
	private String		content;
	
	
//	/**
//	 * The date when the document the CDA represents was created
//	 */
//	private Date		cdaResourceCreationDate;
	
//	/**
//	 * The number of entries in the history table for this bean.
//	 */
//	private Integer		noOfHistoryEntries;
	
	/* ---------------------------------------- */
	// TRANSIENT MEMBERS
	/* ---------------------------------------- */
	
	
	/** The id of the object representing the document in the registry */
	private transient String	documentOid = null;
	
	/**
	 * The description of this document.
	 */
	private transient String	description;
	
	/**
	 * The language of the document 
	 */
	private transient String	languageCode;
	
	/**
	 * The author of this document.
	 */
	private transient String	author;
	
	/**
	 * The size of the CDA document content.
	 */
	private transient Long		contentSize;
	
	/**
	 * The CDA level of this document.
	 */
	private transient Integer	cdaLevel;
	
	/**
	 * The CDA class of this document.
	 */
	private transient String	classCodeId;
	
	/**
	 * The confidentiality of this document.
	 */
	private transient String	confidentialityCodeId;
	
	/**
	 * Specifies if this document is blocked. The is the case, if the 
	 * confidentiality code "T" (taboo) is in the transmission set meta-data
	 * <i>(in addition to the other confidentiality state)</i> 
	 */
	private transient boolean	blocked;
	
	/**
	 * The format code of this document
	 */
	private transient String	formatCode;
	
	/**
	 * The practice settings code of this document
	 */
	private transient String	practiceSettingCode;
	
	/**
	 * The health care facility type code of the document
	 */
	private transient String	hcfTypeCode;
	
	/**
	 * The CDA type of this document.
	 */
	private transient String	typeCodeId;
	
	/** the author specialty code of this document */
	private transient String	authorSpecialty;
	
	/** the author role code of this document */
	private transient String	authorRole;
	
	/**
	 * The home community of this document. (needed in an XCA - Cross Community Access environment)
	 */
	private transient String	homeCommunityId = null;
	
	/**
	 * The creation time of the CDA document resource, collected with the RSQ
	 */
	private transient Date		creationTime;
	
	/**
	 * The upload time of the CDA, collected with the CDA download
	 */
	private transient Date		effectiveTime;
	
	private transient String	authorName;
	
	private transient String	authorId;
	
	private transient String	formattedBytes;
	
	private transient boolean	busy;
	
	private transient boolean	enqueued;
	
	private transient String	mimeType;
	
	private transient String	mediaType;
	
	private transient File		tempFile;
	
	private transient File		clientFile;
	
	private transient String	html;
	
	private transient String	tip;
	
//	private transient boolean	available = true;
	
	
	
	/* ======================================== */
	// CONSTRUCTORS
	/* ======================================== */
	
	public CdaDocument () {}
	
	
	
	/* ======================================== */
	// GETTER & SETTER
	/* ======================================== */
	
	@Column(name = "repository_unique_id")
	public String getRepositoryUniqueId ()
	{
		return repositoryUniqueId;
	}
	
	
	public void setRepositoryUniqueId (String repositoryUniqueId)
	{
		this.repositoryUniqueId = repositoryUniqueId;
	}
	
	
	@Column(name = "dsp_id")
	public Integer getDspId ()
	{
		return dspId;
	}
	
	
	public void setDspId (Integer dspId)
	{
		this.dspId = dspId;
	}
	
	
	@Column(name = "incident_entry_id")
	public Integer getIncidentEntryId ()
	{
		return incidentEntryId;
	}
	
	
	public void setIncidentEntryId (Integer incidentEntryId)
	{
		this.incidentEntryId = incidentEntryId;
	}
	
	
	@Column(name = "download_time")
	public Date getDownloadTime ()
	{
		return downloadTime;
	}
	
	
	public void setDownloadTime (Date gecamedCreationDate)
	{
		this.downloadTime = gecamedCreationDate;
	}
	
	
	@Column(name = "gecamed_origin_ie_id")
	public Integer getOriginIncidentEntryId ()
	{
		return originIncidentEntryId;
	}
	
	
	public void setOriginIncidentEntryId (Integer originIncidentEntryId)
	{
		this.originIncidentEntryId = originIncidentEntryId;
	}
	
	
	@Column(name = "gecamed_filename")
	public String getServerFileName ()
	{
		return serverFileName;
	}
	
	
	public void setServerFileName (String filename)
	{
		this.serverFileName = filename;
	}
	
	
	@Column(name = "read")
	public boolean getRead ()
	{
		return read;
	}
	
	
	public void setRead (Boolean read)
	{
		this.read = read == null ? false : read.booleanValue();
	}
	
	
	@Column(name = "error_msg")
	public String getError ()
	{
		return this.errorMessage;
	}
	
	
	public void setError (String message)
	{
		this.errorMessage = message;
	}
	
	
	@Column(name = "document_unique_id")
	public String getDocumentUniqueId ()
	{
		return documentUniqueId;
	}
	
	
	public void setDocumentUniqueId (String documentUniqueId)
	{
		this.documentUniqueId = documentUniqueId;
	}
	
	
	@Column(name = "version")
	public String getVersion ()
	{
		return version;
	}
	
	
	public void setVersion (String documentVersion)
	{
		this.version = documentVersion;
	}
	
	@Column(name = "title")
	public String getTitle ()
	{
		return title;
	}
	
	
	public void setTitle (String title)
	{
		this.title = title;
		this.html = null;
	}
	
	
	/* ---------------------------------------- */
	// TRANSIENT GETTER & SETTER
	/* ---------------------------------------- */
	
	@Transient
//	@Column(name = "description")
	public String getDescription ()
	{
		return description;
	}
	

	@Transient
	public void setDescription (String description)
	{
		this.description = description;
		this.html = null;
	}
	
	
	@Transient
	public String getLanguageCode ()
	{
		return languageCode;
	}
	

	@Transient
	public void setLanguageCode (String languageCode)
	{
		this.languageCode = languageCode;
		this.html = null;
	}
	
	
	@Transient
	public String getAuthor ()
	{
		return author;
	}
	
	/**
	 * Sets the author. However this string contains besides the author name further information like the id of the used certificate (author id).<br/>
	 * <br/>
	 * <b><i>!! To set the author field is a precondition for the functions {@link #getAuthorName()} and {@link #getAuthorId()} to produce meaningful output !! </i></b>  
	 * @param author The author identification string
	 */
	@Transient
	public void setAuthor (String author)
	{
		this.author	= author;
		authorName	= null;
		this.html	= null;
		this.tip	= null;
	}
	
	@Transient
	public String getAuthorSpecialty() {
		return authorSpecialty;
	}

	@Transient
	public void setAuthorSpecialty(String authorSpecialty) {
		this.authorSpecialty = authorSpecialty;
	}

	@Transient
	public String getDocumentOid() {
		return this.documentOid;
	}

	@Transient
	public void setDocumentOid(String documentOid) {
		this.documentOid = documentOid;
	}

	@Transient
	public String getAuthorRole() {
		return authorRole;
	}

	@Transient
	public void setAuthorRole(String authorRole) {
		this.authorRole = authorRole;
	}
	
	@Transient
	public String getClassCode ()
	{
		return classCodeId;
	}
	
	
	@Transient
	public void setClassCode (String code)
	{
		this.classCodeId = code;
	}
	
	
	@Transient
	public String getTypeCode ()
	{
		return typeCodeId;
	}
	

	@Transient
	public void setTypeCode (String typeCode)
	{
		this.typeCodeId = typeCode;
	}
	
	
	@Transient
	public String getConfidentialityCode ()
	{
		return confidentialityCodeId;
	}
	
	
	@Transient
	public boolean isBlocked ()
	{
		return blocked;
	}
	

	@Transient
	public void setConfidentialityStatus (String confidentialityCode, boolean blocked)
	{
		this.confidentialityCodeId = confidentialityCode;
		this.blocked = blocked;
	}
	
	
	@Transient
	public Integer getLevel ()
	{
		return cdaLevel;
	}
	

	@Transient
	public void setLevel (Integer level)
	{
		this.cdaLevel = level;
	}
	
	
	/**
	 * @return A date specifying, when the resource, this CDA descends from was created.
	 */
	@Transient
	public Date getCreationTime ()
	{
		return creationTime;
	}
	

	@Transient
	public void setCreationTime (Date creationTime)
	{
		this.creationTime = creationTime;
		this.html	= null;
		this.tip	= null;
	}
	
	
	/**
	 * @return A date specifying, when the CDA was uploaded
	 */
	@Transient
	public Date getEffectiveTime ()
	{
		return effectiveTime;
	}
	
	
//	@Transient
//	public void setEffectiveTime (Date time)
//	{
//		this.effectiveTime = time;
//	}
	
	
	@Transient
	public void setEffectiveTime (Date effectiveTime)
	{
		this.effectiveTime = effectiveTime;
		this.html = null;
	}
	
	
	@Transient
	public Long getContentSize ()
	{
		return contentSize;
	}
	
	
	@Transient
	public void setContentSize (Long size)
	{
		this.contentSize	= size;
		this.formattedBytes	= null;
		this.html			= null;
		this.tip			= null;
	}
	
	
//	@Column(name = "history_entries")
//	public Integer getNoOfHistoryEntries ()
//	{
//		return noOfHistoryEntries;
//	}
//	
//	
//	private void setNoOfHistoryEntries (Integer noOfEntries)
//	{
//		this.noOfHistoryEntries = noOfEntries;
//	}
	
	
	@Transient
	public String getFormatCode ()
	{
		return formatCode;
	}
	
	
	public void setFormatCode (String formatCode)
	{
		this.formatCode = formatCode;
	}
	
	
	@Transient
	public String getPracticeSettingCode ()
	{
		return practiceSettingCode;
	}
	
	
	@Transient
	public void setPracticeSettingCode (String practiceSettingCode)
	{
		this.practiceSettingCode = practiceSettingCode;
	}
	
	
	@Transient
	public String getHcfTypeCode ()
	{
		return hcfTypeCode;
	}
	

	@Transient
	public void setHcfTypeCode (String hcfTypeCode)
	{
		this.hcfTypeCode = hcfTypeCode;
	}
	
	@Transient
	public String getHomeCommunity ()
	{
		return this.homeCommunityId;
	}

	
	@Transient
	public void setHomeCommunity(String homeCommunityId) {
		
		this.homeCommunityId = ((homeCommunityId != null) && (homeCommunityId.length() > 0)) ? homeCommunityId : null;
	}
	
	@Transient
	public int getStatus ()
	{
		if (errorMessage != null)
		{
			return CdaDocument.STATUS_ERROR;
		}
		else if (!read)
		{
			if (downloadTime == null)
			{
				return CdaDocument.STATUS_NEW;
			}
			else if (!isDownloaded())
			{
				Calendar	tomorrow	= new GregorianCalendar();
				tomorrow.setTime(downloadTime);
				tomorrow.set(Calendar.HOUR_OF_DAY,	0);
				tomorrow.set(Calendar.MINUTE,		0);
				tomorrow.set(Calendar.SECOND,		0);
				tomorrow.set(Calendar.MILLISECOND,	0);
				tomorrow.add(Calendar.DAY_OF_MONTH,	1);
				
				if (tomorrow.getTimeInMillis() > new Date().getTime())
					return CdaDocument.STATUS_NEW;
				else
					return CdaDocument.STATUS_UNREAD;
			}
			else if (incidentEntryId == null)
			{
				return CdaDocument.STATUS_UNREAD_AND_DOWNLOADED;
			}
			else if (originIncidentEntryId == null)
			{
				return CdaDocument.STATUS_UNREAD_AND_INTEGRATED;
			}
			else
			{
				return CdaDocument.STATUS_UPLOADED;
			}
		}
		else 
		{
			if (!isDownloaded())
				return CdaDocument.STATUS_READ;
			else if (incidentEntryId == null)
				return CdaDocument.STATUS_READ_AND_DOWNLOADED;
			else if (originIncidentEntryId == null)
				return CdaDocument.STATUS_READ_AND_INTEGRATED;
			else
				return CdaDocument.STATUS_UPLOADED;
		}
	}
	
	
	@Transient
	public String getMediaType ()
	{
		return mediaType;
	}
	
	
	@Transient
	public void setMediaType (String mediaType)
	{
		this.mediaType = mediaType;
	}
	
	
	@Transient
	public String getMimeType ()
	{
		return mimeType;
	}
	
	
	@Transient
	public void setMimeType (String mimeType)
	{
		this.mimeType = mimeType;
	}
	
	
	/**
	 * Provides the name of the author in the format <Prename, NAME>
	 * 
	 * @return The author name or null, if the author field has not yet been set with {@link #setAuthor(String)}
	 */
	@Transient
	public String getAuthorName ()
	{
		if (authorName == null && author != null && author.contains("^"))
		{
			String[] authorFields = author.split("\\^");
			if (authorFields.length < 2)
				return null;
			
			StringBuilder builder = new StringBuilder(authorFields[1]);
			
			if (authorFields[2] != null)
				builder.append(", ")
						.append(authorFields[2]);
			
			authorName = builder.toString().trim();
		}
		
		return authorName;
	}

	/**
	 * Provides the id of the author (which is the id of the used certificate)
	 * 
	 * @return The author id or null, if the author field has not yet been set with {@link #setAuthor(String)}
	 */
	@Transient
	public String getAuthorId ()
	{
		if (authorId == null && author != null && author.contains("^"))
		{	
			authorId = author.substring(0,author.indexOf('^'));
		}
		
		return authorId;
	}

	
	@Transient
	public String getContent ()
	{
		return content;
	}
	
	
	@Transient
	public void setContent (String content)
	{
		this.content = content;
	}
	
	
	@Transient
	public Dsp getDsp ()
	{
		return dsp;
	}
	
	
	@Transient
	public void setDsp (Dsp dsp) throws InvalidParameterException
	{
		this.dsp = dsp;
		if (dsp != null)
			this.dspId = dsp.getId();
		else
			this.dspId = null;
		
//		if (dsp == null
//				|| (dspId == null && dsp.getId() == null)
//				|| (dspId != null && dspId.equals(dsp.getId())))
//			this.dsp = dsp;
//		else
//			throw new InvalidParameterException("DSP id of set dsp must be identical to the set dspId.");
	}
	
	
	/**
	 * @return The file containing the content of the CDA (PDF or CDA 2 / 3 without meta data)
	 */
	@Transient
	public File getContentClientFile ()
	{
		return tempFile;
	}
	
	
	/**
	 * @param file Set the file containing the content of the CDA (PDF or CDA 2 / 3 without meta data)
	 */
	@Transient
	public void setContentClientFile (File file)
	{
		this.tempFile	= file;
	}
	
	
	@Transient
	public boolean isBusy ()
	{
		return busy;
	}
	
	
	@Transient
	public void setBusy (boolean isBusy)
	{
		this.busy = isBusy;
	}
	
	
	@Transient
	public boolean isEnqueued ()
	{
		return enqueued;
	}
	
	
	@Transient
	public void setEnqueued (boolean enqueued)
	{
		this.enqueued = enqueued;
	}


	@Transient
	public String toHtml (boolean includeDescription)
	{
		if (html == null)
		{
			StringBuilder	label			= new StringBuilder("<html>");
			String			description		= getDescription();
			String			title			= getTitle();
			Date			creationTime	= getCreationTime();
//			Date			effectiveTime	= getEffectiveTime();
			String			size			= getFormattedBytes();
			String			author			= getAuthorName();
			String			language		= getTranslatedLanguage();
//			String			confidentiality	= getConfidentialityCode();
			boolean			addSeparator	= false;
			String			separator		= " | ";
			
			
			if (title != null)
				label.append("<font size=4><b>")
						.append(title)
						.append("</b></font><br>");
			
			if (includeDescription && description != null)
				label.append("<br><font size=3><i>")
						.append(description.replace("\n", "<br>"))
						.append("</i></font>");
			
			label.append("<font size=2><i>");
			if (creationTime != null)
			{
				label.append(GECAMedUtils.getLocaleTimeFormatter().format(creationTime));
				addSeparator	= true;
			}
			
//			if (effectiveTime != null)
//			{
//				if (addSeparator)
//					label.append(separator);
//				else
//					addSeparator = true;
//				label.append(GECAMedUtils.getLocaleTimeFormatter().format(effectiveTime));
//			}
			
			if (!GECAMedUtils.isEmpty(author))
			{
				if (addSeparator)
					label.append(separator);
				else
					addSeparator = true;
				label.append(author);
			}
			
			if (language != null && language.trim().length() > 0)
			{
				if (addSeparator)
					label.append(separator);
				else
					addSeparator = true;
				label.append(language);
			}
			
			if (size != null)
			{
				if (addSeparator)
					label.append(separator);
				else
					addSeparator = true;
				label.append(size);
			}
			
//			if (confidentiality != null)
//			{
//				if (addSeparator)
//					label.append(separator);
//				else 
//					addSeparator = true;
//				label.append(confidentiality);
//			}
			
			
			// close the tags
			label.append("</i></font></html>");
			html = label.toString();
		}
		
		return html;
	}
	
	
	@Transient
	public String getToolTip ()
	{
		if (tip == null)
		{
			if (errorMessage != null && errorMessage.trim().length() > 0)
				tip = errorMessage;
			else
			{
				StringBuilder	tipBuilder		= new StringBuilder("<html>");
				DateFormat		timeFormatter	= GECAMedUtils.getLocaleTimeFormatter();
				String			size			= getFormattedBytes();
				String			author			= getAuthorName();
				
				
				if (!GECAMedUtils.isEmpty(description))
					tipBuilder.append(description.replaceAll("\\</(?i)html\\>", "")).append("<br>");
				
				tipBuilder.append("<font size=2><table border=\"0\" cellspacing=\"0\" cellpadding=\"0\">");
				
				if (creationTime != null)
					tipBuilder.append("<tr><td>")
							.append(Translatrix.getTranslationString("esante.CdaList.tip.creationTime"))
							.append("<td></td>")
							.append(timeFormatter.format(creationTime))
							.append("</td></tr>");
				if (effectiveTime != null)
					tipBuilder.append("<tr><td>")
							.append(Translatrix.getTranslationString("esante.CdaList.tip.effectiveTime"))
							.append("<td></td>")
							.append(timeFormatter.format(effectiveTime))
							.append("</td></tr>");
				if (author != null)
					tipBuilder.append("<tr><td>")
							.append(Translatrix.getTranslationString("esante.CdaList.tip.author"))
							.append("<td></td>")
							.append(author)
							.append("</td></tr>");
				if (size != null)
					tipBuilder.append("<tr><td>")
							.append(Translatrix.getTranslationString("esante.CdaList.tip.size"))
							.append("<td></td>")
							.append(size)
							.append("</li>");
				if (confidentialityCodeId != null)
					tipBuilder.append("<tr><td>")
							.append(Translatrix.getTranslationString("esante.CdaList.tip.confidentialityCode"))
							.append("<td></td>")
							.append(Translatrix.getTranslationString("esante.CdaList.tip.confidentialityCode."+confidentialityCodeId))
							.append("</td></tr>");
				if (isBlocked())
					tipBuilder.append("<tr>")
							.append("<td span=2><b>")
							.append(Translatrix.getTranslationString("esante.CdaList.tip.blocked"))
							.append("</b></td></tr>");
				
				tipBuilder.append("</table></font></html>");
				tip	= tipBuilder.toString();
			}
		}
		
		return tip;
	}
	
	
//	private String translatedLanguage;
	@Transient
	public String getTranslatedLanguage ()
	{
//		if (translatedLanguage == null && languageCode != null)
//		{
//			String[] languageCodeFields	= languageCode.split("[^a-zAZ]+");
//			if (languageCodeFields.length < 1)
//				return null;
//			Translatrix.getTranslationString(""+languageCodeFields[0]);
//		}
//		return translatedLanguage;
		return getLanguageCode();
	}
	
	
	@Transient
	public String getFormattedBytes ()
	{
		if (formattedBytes == null 
				&& getContentSize() != null)
			formattedBytes = formatBytes(getContentSize());
		return formattedBytes;
	}
	
	
	@Transient
	@Override
	public int compareTo (GECAMedEntityBean o)
	{
		if (o instanceof CdaDocument)
		{
			CdaDocument document = (CdaDocument)o;
			
			if (document.getCreationTime() == null)
			{
				return -1;
			}
			else if (document.getCreationTime() == null)
			{
				return 1;
			}
			else if (!this.getCreationTime()
					.equals(document.getCreationTime()))
			{
				// sort the other way round -> o.compareTo(this) instead this.compareTo(o)
				return document.getCreationTime()
						.compareTo(this.getCreationTime());
			}
			else
			{
				return this.toString().compareTo(o.toString());
			}
		}
		else
		{
			return super.compareTo(o);
		}
	}
	
	
	@Transient
	public boolean canBeIntegrated ()
	{
		return getIncidentEntryId() == null 
				&& getOriginIncidentEntryId() == null 
				&& getError() == null;
	}
	
	
	@Transient
	public boolean canBeDisintegrated ()
	{
		return getIncidentEntryId() != null;
	}
	
	
	@Transient
	public boolean isDownloaded ()
	{
		return getServerFileName() != null
				|| getClientFile() != null;
	}
	
	
	@Transient
	public boolean canDeleteLocalContent ()
	{
		return getServerFileName() != null
				&& getIncidentEntryId() == null;
	}
	
	
	/**
	 * @return The downloaded CDA XML file (including the meta data) as cached on the client 
	 * or <code>null</code> if the file is not in the cache.
	 */
	@Transient
	public File getClientFile ()
	{
		return clientFile;
	}
	
	
	/**
	 * @param file The downloaded CDA XML file (including the meta data) as cached on the client 
	 * or <code>null</code> if the file is not in the cache.
	 */
	@Transient
	public void setClientFile (File file)
	{
		this.clientFile = file;
	}
	
	
	
	/* ======================================== */
	// HELP METHODS
	/* ======================================== */
	
	@Transient
	private static String formatBytes (long bytes)
	{
		char			decimalSeparator;
		char			thousandsSeparator;
		String 			byteString		= String.valueOf(bytes);
		int				unitIndex		= byteString.length() / 3;
		int				startIndex		= byteString.length() % 3;
		Locale			defaultLocale	= Locale.getDefault();
		StringBuilder	out;
		
		
		// get the decimal and thousands separator
		if (defaultLocale.getLanguage().equals("en"))
		{
			decimalSeparator	= '.';
			thousandsSeparator	= ',';
		}
		else
		{
			decimalSeparator	= ',';
			thousandsSeparator	= '.';
		}
		
		out	= new StringBuilder(byteString.length() + byteString.length() / 3 + 1);
		// calculate the start and unit index and build the formated string
		if (startIndex == 0 && unitIndex > 0)
		{
			unitIndex--;
			startIndex	+= 3;
		}
		if (unitIndex >= BYTE_UNITS.length)
		{
			startIndex	+= (unitIndex - BYTE_UNITS.length) * 3;
			unitIndex	= BYTE_UNITS.length - 1;
			
			// the number before the decimal sign
			out.append(byteString.substring(0, startIndex)
					.replaceAll("(?<=\\d)(?=\\d{3}($|"+thousandsSeparator+"))", 
							String.valueOf(thousandsSeparator)));
		}
		else
		{
			// the number before the decimal sign
			out.append(byteString.substring(0, startIndex));
		}
		
		// the decimal separator
		out.append(decimalSeparator)
				// the 3 numbers behind the decimal sign
				.append(byteString.substring(startIndex, (startIndex + 3 <= byteString.length() ? startIndex + 3 : byteString.length())))
				// the byte unit
				.append(BYTE_UNITS[unitIndex]);
		
		return out.toString().replaceAll(
				decimalSeparator+"?0+(?="+BYTE_UNITS[unitIndex]+")", "");
	}
	
	
	@Transient
	public void updateNonTransient (CdaDocument target)
	{
		Class<?>	clazz			= this.getClass();
		List<Field>	fieldList		= new LinkedList<Field>();
		Field[]		fields;
		Field		targetField;
		
		
		while (clazz != null)
		{
			fields	= clazz.getDeclaredFields();
			for (Field f : fields)
			{
				if ((f.getModifiers() & REFLECTION_MODIFIER_TEST) == 0)
					fieldList.add(f);
			}
			
			clazz	= clazz.getSuperclass();
		}
		
		for (Field sourceField : fieldList)
		{
			try
			{
				clazz = this.getClass();
				while (clazz != null)
				{
					try
					{
						targetField	= clazz.getDeclaredField(sourceField.getName());
					}
					catch (NoSuchFieldException e)
					{
						// try the super class
						clazz = clazz.getSuperclass();
						continue;
					}
					targetField.setAccessible(true);
					sourceField.setAccessible(true);
					targetField.set(target, sourceField.get(this));
					break;
				}
			}
			catch (SecurityException e)
			{
				logger.error("Couldn't copy properties, because of a security manager.", e);
			}
			catch (IllegalArgumentException e)
			{
				// shouldn't happen, but show the exception anyway
				logger.error("Couldn't copy properties, because tThe object types doesn't seem to match.", e);
			}
			catch (IllegalAccessException e)
			{
				// shouldn't happen, but show the exception anyway
				logger.error("Couldn't copy properties, becuase the access to the property is forbidden.", e);
			}
		}
	}
}
