All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.ehrbase.dao.access.jooq.EntryAccess Maven / Gradle / Ivy

There is a newer version: 2.12.0
Show newest version
/*
 * Copyright (c) 2019 vitasystems GmbH and Hannover Medical School.
 *
 * This file is part of project EHRbase
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.ehrbase.dao.access.jooq;

import static org.ehrbase.jooq.pg.Tables.ENTRY;
import static org.ehrbase.jooq.pg.Tables.ENTRY_HISTORY;
import static org.ehrbase.jooq.pg.Tables.TERRITORY;

import com.nedap.archie.rm.archetyped.Archetyped;
import com.nedap.archie.rm.archetyped.FeederAudit;
import com.nedap.archie.rm.archetyped.Link;
import com.nedap.archie.rm.archetyped.TemplateId;
import com.nedap.archie.rm.composition.Action;
import com.nedap.archie.rm.composition.AdminEntry;
import com.nedap.archie.rm.composition.Composition;
import com.nedap.archie.rm.composition.Evaluation;
import com.nedap.archie.rm.composition.EventContext;
import com.nedap.archie.rm.composition.Instruction;
import com.nedap.archie.rm.composition.Observation;
import com.nedap.archie.rm.composition.Section;
import com.nedap.archie.rm.datatypes.CodePhrase;
import com.nedap.archie.rm.datavalues.DvCodedText;
import com.nedap.archie.rm.datavalues.DvText;
import com.nedap.archie.rm.generic.PartyProxy;
import com.nedap.archie.rm.support.identification.ArchetypeID;
import com.nedap.archie.rm.support.identification.ObjectVersionId;
import com.nedap.archie.rm.support.identification.TerminologyId;
import com.nedap.archie.rm.support.identification.UIDBasedId;
import java.sql.Timestamp;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import org.ehrbase.api.exception.InternalServerException;
import org.ehrbase.dao.access.interfaces.I_CompositionAccess;
import org.ehrbase.dao.access.interfaces.I_ContextAccess;
import org.ehrbase.dao.access.interfaces.I_DomainAccess;
import org.ehrbase.dao.access.interfaces.I_EntryAccess;
import org.ehrbase.dao.access.jooq.party.PersistedPartyProxy;
import org.ehrbase.dao.access.support.DataAccess;
import org.ehrbase.dao.access.support.SafeNav;
import org.ehrbase.jooq.pg.enums.EntryType;
import org.ehrbase.jooq.pg.tables.records.EntryHistoryRecord;
import org.ehrbase.jooq.pg.tables.records.EntryRecord;
import org.ehrbase.jooq.pg.udt.records.DvCodedTextRecord;
import org.ehrbase.serialisation.dbencoding.RawJson;
import org.ehrbase.serialisation.dbencoding.rmobject.FeederAuditEncoding;
import org.ehrbase.serialisation.dbencoding.rmobject.LinksEncoding;
import org.ehrbase.service.RecordedDvCodedText;
import org.ehrbase.service.RecordedDvText;
import org.jooq.Condition;
import org.jooq.JSONB;
import org.jooq.Record;
import org.jooq.UpdateQuery;
import org.jooq.impl.DSL;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Operations on the Entry part of a Composition (Entry is archetyped).
 *
 * @author Christian Chevalley
 * @author Jake Smolka
 * @author Luis Marco-Ruiz
 * @since 1.0.0
 */
public class EntryAccess extends DataAccess implements I_EntryAccess {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    public static final String DB_INCONSISTENCY = "DB inconsistency:";

    private EntryRecord entryRecord;

    private Composition composition;

    /**
     * Constructor with convenient {@link I_DomainAccess} parameter, for better readability.
     *
     * @param domainAccess  Current domain access object
     * @param templateId    Template ID of this entry
     * @param sequence      Sequence number of this entry
     * @param compositionId Linked composition ID
     * @param composition   Object representation of linked composition
     */
    public EntryAccess(
            I_DomainAccess domainAccess,
            String templateId,
            Integer sequence,
            UUID compositionId,
            Composition composition,
            Short sysTenant) {
        super(
                domainAccess.getContext(),
                domainAccess.getKnowledgeManager(),
                domainAccess.getIntrospectService(),
                domainAccess.getServerConfig());
        setFields(templateId, sequence, compositionId, composition, sysTenant);
    }

    private EntryAccess(I_DomainAccess domainAccess) {
        super(domainAccess);
    }

    public static String fetchTemplateIdByCompositionId(I_DomainAccess domainAccess, UUID compositionId) {
        return domainAccess
                .getContext()
                .select(ENTRY.TEMPLATE_ID)
                .from(ENTRY)
                .where(ENTRY.COMPOSITION_ID.equal(compositionId))
                .fetchOptional(ENTRY.TEMPLATE_ID)
                .orElse(null);
    }

    /**
     * @throws IllegalArgumentException if DB is inconsistent or operation fails
     */
    public static I_EntryAccess retrieveInstanceInComposition(
            I_DomainAccess domainAccess, I_CompositionAccess compositionAccess) {

        Optional existingEntry =
                domainAccess.getContext().fetchOptional(ENTRY, ENTRY.COMPOSITION_ID.eq(compositionAccess.getId()));
        if (existingEntry.isEmpty()) {
            return null;
        }

        // build the list of parameters to recreate the composition
        Map values = new EnumMap<>(SystemValue.class);
        values.put(
                SystemValue.COMPOSER,
                new PersistedPartyProxy(domainAccess).retrieve(compositionAccess.getComposerId()));

        // optional handling for persistent compositions that do not have a context
        Optional opContextAccess =
                compositionAccess.getContextId().map(id -> I_ContextAccess.retrieveInstance(domainAccess, id));
        opContextAccess.ifPresent(context -> values.put(SystemValue.CONTEXT, context.mapRmEventContext()));

        values.put(
                SystemValue.LANGUAGE,
                new CodePhrase(new TerminologyId("ISO_639-1"), compositionAccess.getLanguageCode()));
        String territory2letters = domainAccess
                .getContext()
                .fetchSingle(TERRITORY, TERRITORY.CODE.eq(compositionAccess.getTerritoryCode()))
                .getTwoletter();

        values.put(SystemValue.TERRITORY, new CodePhrase(new TerminologyId("ISO_3166-1"), territory2letters));

        if (compositionAccess.getFeederAudit() != null) {
            values.put(SystemValue.FEEDER_AUDIT, new FeederAuditEncoding().fromDB(compositionAccess.getFeederAudit()));
        }

        if (compositionAccess.getLinks() != null) {
            values.put(SystemValue.LINKS, new LinksEncoding().fromDB(compositionAccess.getLinks()));
        }

        try {
            EntryAccess entryAccess = new EntryAccess(domainAccess);

            // set the record UID in the composition with matching version number
            Integer version = I_CompositionAccess.getLastVersionNumber(domainAccess, compositionAccess.getId());
            values.put(
                    SystemValue.UID,
                    new ObjectVersionId(compositionAccess.getId().toString() + "::"
                            + domainAccess.getServerConfig().getNodename() + "::" + version));

            EntryRecord entryRecord = existingEntry.get();
            entryAccess.entryRecord = entryRecord;
            String value = entryRecord.getEntry().data();
            entryAccess.composition = new RawJson().unmarshal(value, Composition.class);

            // continuing optional handling for persistent compositions
            opContextAccess
                    .map(I_ContextAccess::mapRmEventContext)
                    .ifPresent(ec -> values.put(SystemValue.CONTEXT, ec));
            values.put(SystemValue.CATEGORY, new RecordedDvCodedText().fromDB(entryRecord, ENTRY.CATEGORY));
            setCompositionAttributes(entryAccess.composition, values);
            buildArchetypeDetails(entryAccess);

            return entryAccess;
        } catch (Exception e) {
            throw new IllegalArgumentException(DB_INCONSISTENCY + e);
        }
    }

    private static void buildArchetypeDetails(EntryAccess entryAccess) {
        Archetyped archetypeDetails = new Archetyped();
        TemplateId templateId = new TemplateId();
        templateId.setValue(entryAccess.getTemplateId());
        archetypeDetails.setTemplateId(templateId);
        archetypeDetails.setArchetypeId(new ArchetypeID(entryAccess.getArchetypeId()));
        archetypeDetails.setRmVersion(entryAccess.getRmVersion());
        entryAccess.composition.setArchetypeDetails(archetypeDetails);
    }

    public static I_EntryAccess retrieveInstanceInCompositionVersion(
            I_DomainAccess domainAccess, I_CompositionAccess compositionHistoryAccess, int version) {

        Condition condition = ENTRY_HISTORY
                .COMPOSITION_ID
                .eq(compositionHistoryAccess.getId())
                .and(ENTRY_HISTORY.SYS_TRANSACTION.eq(compositionHistoryAccess.getSysTransaction()));

        Optional existingEntryHistory =
                domainAccess.getContext().fetchOptional(ENTRY_HISTORY, condition);
        if (existingEntryHistory.isEmpty()) {
            return null;
        }

        // build the list of parameters to recreate the composition
        Map values = new EnumMap<>(SystemValue.class);
        values.put(
                SystemValue.COMPOSER,
                new PersistedPartyProxy(domainAccess).retrieve(compositionHistoryAccess.getComposerId()));

        EventContext context = I_ContextAccess.retrieveHistoricalEventContext(
                domainAccess, compositionHistoryAccess.getId(), compositionHistoryAccess.getSysTransaction());
        if (context == null) { // unchanged context use the current one!
            // also optional handling of context, because persistent compositions don't have a context
            compositionHistoryAccess.getContextId().ifPresent(uuid -> I_ContextAccess.retrieveInstance(
                            domainAccess, uuid)
                    .mapRmEventContext());
        }
        values.put(SystemValue.CONTEXT, context);

        values.put(
                SystemValue.LANGUAGE,
                new CodePhrase(new TerminologyId("ISO_639-1"), compositionHistoryAccess.getLanguageCode()));
        String territory2letters = domainAccess
                .getContext()
                .fetchSingle(TERRITORY, TERRITORY.CODE.eq(compositionHistoryAccess.getTerritoryCode()))
                .getTwoletter();
        values.put(SystemValue.TERRITORY, new CodePhrase(new TerminologyId("ISO_3166-1"), territory2letters));

        values.put(
                SystemValue.FEEDER_AUDIT, new FeederAuditEncoding().fromDB(compositionHistoryAccess.getFeederAudit()));

        try {
            EntryAccess entryAccess = new EntryAccess(domainAccess);

            // set the record UID in the composition
            UUID compositionId = compositionHistoryAccess.getId();
            values.put(
                    SystemValue.UID,
                    new ObjectVersionId(compositionId.toString() + "::"
                            + domainAccess.getServerConfig().getNodename() + "::" + version));

            EntryHistoryRecord entryHistoryRecord = existingEntryHistory.get();
            entryAccess.entryRecord = domainAccess.getContext().newRecord(ENTRY);
            entryAccess.entryRecord.setSysTenant(entryHistoryRecord.getSysTenant());
            entryAccess.entryRecord.from(entryHistoryRecord);
            entryAccess.composition =
                    new RawJson().unmarshal(entryHistoryRecord.getEntry().data(), Composition.class);

            DvCodedTextRecord category = entryHistoryRecord.getCategory();

            //            SafeNav safeDvCodedText = SafeNav
            //                .of(category)
            //                .get(c -> c.getValue())
            //                .get(s -> new DvCodedText(s, (CodePhrase) null))
            //                .use(SafeNav.of(category).get(c -> c.getDefiningCode()).get(d -> d.getCodeString()))
            //                .get((s, d) -> {d.setDefiningCode(new CodePhrase(s)); return d;})
            //            values.put(SystemValue.CATEGORY, safeDvCodedText.get())
            SafeNav safeDvCodedText = SafeNav.of(category)
                    .get(c -> new DvCodedText(c.getValue(), c.getDefiningCode().getCodeString()));
            values.put(SystemValue.CATEGORY, safeDvCodedText.get());

            setCompositionAttributes(entryAccess.composition, values);
            buildArchetypeDetails(entryAccess);

            return entryAccess;
        } catch (Exception e) {
            throw new IllegalArgumentException(DB_INCONSISTENCY + e);
        }
    }

    private static void setCompositionAttributes(Composition composition, Map values) {

        if (values == null) {
            return;
        }

        for (Map.Entry systemValue : values.entrySet()) {
            switch (systemValue.getKey()) {
                case CATEGORY:
                    composition.setCategory((DvCodedText) systemValue.getValue());
                    break;
                case LANGUAGE:
                    composition.setLanguage((CodePhrase) systemValue.getValue());
                    break;
                case TERRITORY:
                    composition.setTerritory((CodePhrase) systemValue.getValue());
                    break;
                case COMPOSER:
                    composition.setComposer((PartyProxy) systemValue.getValue());
                    break;
                case UID:
                    composition.setUid((UIDBasedId) systemValue.getValue());
                    break;
                case CONTEXT:
                    composition.setContext((EventContext) systemValue.getValue());
                    break;
                case NAME:
                    composition.setName((DvText) systemValue.getValue());
                    break;

                case RM_VERSION:
                    composition.getArchetypeDetails().setRmVersion((String) systemValue.getValue());
                    break;

                case FEEDER_AUDIT:
                    composition.setFeederAudit((FeederAudit) systemValue.getValue());
                    break;

                case LINKS:
                    composition.setLinks((List) systemValue.getValue());
                    break;

                default:
                    throw new IllegalArgumentException(
                            "Could not handle composition attribute:" + systemValue.getKey());
            }
        }
    }

    /**
     * set the EntryRecord with fields from composition:
*
    *
  • category
  • *
  • item type
  • *
  • archetype node Id
  • *
  • entry content (json)
  • *
* * @param entryRecord Target {@link EntryRecord} * @param composition input data in {@link Composition} representation */ private void setCompositionFields(EntryRecord entryRecord, Composition composition) { entryRecord.setCategory(entryRecord.getCategory()); if (composition.getContent() != null && !composition.getContent().isEmpty()) { Object node = composition.getContent().get(0); if (node instanceof Section) { entryRecord.setItemType(EntryType.valueOf("section")); } else if (node instanceof Evaluation || node instanceof Observation || node instanceof Instruction || node instanceof Action) { entryRecord.setItemType(EntryType.valueOf("care_entry")); } else if (node instanceof AdminEntry) { entryRecord.setItemType(EntryType.valueOf("admin")); } } else { entryRecord.setItemType(EntryType.valueOf("admin")); } entryRecord.setArchetypeId(composition.getArchetypeNodeId()); RawJson rawJson = new RawJson(); entryRecord.setEntry(JSONB.valueOf(rawJson.marshal(composition))); } /** * Sets the field of a new Entry record (as part of the calling {@link EntryAccess} instance) with * given parameters. The Composition of the calling {@link EntryAccess} will be updated with the * given {@link Composition} as a result. * * @param templateId ID of template * @param sequence Sequence number * @param compositionId ID of composition * @param composition {@link Composition} object with more information for the entry */ private void setFields( String templateId, Integer sequence, UUID compositionId, Composition composition, Short sysTenant) { entryRecord = getContext().newRecord(ENTRY); entryRecord.setTemplateId(templateId); entryRecord.setSequence(sequence); entryRecord.setCompositionId(compositionId); entryRecord.setRmVersion(composition.getArchetypeDetails().getRmVersion()); entryRecord.setSysTenant(sysTenant); new RecordedDvCodedText().toDB(entryRecord, ENTRY.CATEGORY, composition.getCategory()); setCompositionFields(entryRecord, composition); setCompositionName(composition.getName()); this.composition = composition; } @Override public Composition getComposition() { return composition; } @Override public UUID commit(Timestamp transactionTime) { // use jOOQ Record result = getContext() .insertInto( ENTRY, ENTRY.SEQUENCE, ENTRY.COMPOSITION_ID, ENTRY.TEMPLATE_ID, ENTRY.ITEM_TYPE, ENTRY.ARCHETYPE_ID, ENTRY.CATEGORY, ENTRY.ENTRY_, ENTRY.SYS_TRANSACTION, ENTRY.NAME, ENTRY.RM_VERSION, ENTRY.SYS_TENANT) .values( DSL.val(getSequence()), DSL.val(getCompositionId()), DSL.val(getTemplateId()), DSL.val(EntryType.valueOf(getItemType())), DSL.val(getArchetypeId()), DSL.val(getCategory()), DSL.val(getEntryJson()), DSL.val(transactionTime), DSL.val(getCompositionName()), DSL.val(getRmVersion()), // we do not expose the namespace DSL.val(entryRecord.getSysTenant())) .returning(ENTRY.ID) .fetchOne(); return result.getValue(ENTRY.ID); } /** * @throws InternalServerException because inherited interface function isn't implemented in this * class * @deprecated */ @Deprecated(forRemoval = true) @Override public UUID commit() { throw new InternalServerException("INTERNAL: commit without transaction time is not legal"); } @Override public Boolean update(Timestamp transactionTime) { return update(transactionTime, false); } @Override public Boolean update(Timestamp transactionTime, boolean force) { logger.debug("updating entry with force flag: {} and changed flag: {}", force, entryRecord.changed()); if (!(force || entryRecord.changed())) { logger.debug("No updateComposition took place, returning..."); return false; } // ignore the temporal field since it is maintained by an external trigger! entryRecord.changed(ENTRY.SYS_PERIOD, false); UpdateQuery updateQuery = getContext().updateQuery(ENTRY); updateQuery.addValue(ENTRY.COMPOSITION_ID, getCompositionId()); updateQuery.addValue(ENTRY.SEQUENCE, DSL.field(DSL.val(getSequence()))); updateQuery.addValue(ENTRY.TEMPLATE_ID, DSL.field(DSL.val(getTemplateId()))); updateQuery.addValue(ENTRY.ITEM_TYPE, DSL.field(DSL.val(EntryType.valueOf(getItemType())))); updateQuery.addValue(ENTRY.ARCHETYPE_ID, DSL.field(DSL.val(getArchetypeId()))); updateQuery.addValue(ENTRY.CATEGORY, DSL.field(DSL.val(getCategory()))); updateQuery.addValue(ENTRY.ENTRY_, DSL.field(DSL.val(getEntryJson()))); updateQuery.addValue(ENTRY.SYS_TRANSACTION, DSL.field(DSL.val(transactionTime))); updateQuery.addValue(ENTRY.NAME, DSL.field(DSL.val(getCompositionName()))); updateQuery.addValue(ENTRY.RM_VERSION, DSL.field(DSL.val(getRmVersion()))); updateQuery.addConditions(ENTRY.ID.eq(getId())); logger.debug("Update done..."); return updateQuery.execute() > 0; } /** * @throws InternalServerException because inherited interface function isn't implemented in this * class * @deprecated */ @Deprecated(forRemoval = true) @Override public Boolean update() { throw new InternalServerException( "INTERNAL: Invalid updateComposition call to updateComposition without Transaction time and/or force flag arguments"); } /** * @throws InternalServerException because inherited interface function isn't implemented in this * class * @deprecated */ @Deprecated(forRemoval = true) @Override public Boolean update(Boolean force) { throw new InternalServerException( "INTERNAL: Invalid updateComposition call to updateComposition without Transaction time and/or force flag arguments"); } @Override public Integer delete() { if (entryRecord != null) { return entryRecord.delete(); } return 0; } @Override public UUID getId() { return entryRecord.getId(); } @Override public JSONB getEntryJson() { return entryRecord.getEntry(); } @Override public DvCodedTextRecord getCategory() { return entryRecord.getCategory(); } public DvCodedTextRecord getCompositionName() { return entryRecord.getName(); } public void setCompositionName(DvText compositionName) { new RecordedDvText().toDB(entryRecord, ENTRY.NAME, compositionName); } @Override public UUID getCompositionId() { return entryRecord.getCompositionId(); } @Override public void setCompositionId(UUID compositionId) { entryRecord.setCompositionId(compositionId); } @Override public String getTemplateId() { return entryRecord.getTemplateId(); } @Override public void setTemplateId(String templateId) { entryRecord.setTemplateId(templateId); } @Override public Integer getSequence() { return entryRecord.getSequence(); } @Override public void setSequence(Integer sequence) { entryRecord.setSequence(sequence); } @Override public String getArchetypeId() { return entryRecord.getArchetypeId(); } @Override public String getRmVersion() { return entryRecord.getRmVersion(); } @Override public String getItemType() { return entryRecord.getItemType().getLiteral(); } @Override public void setCompositionData(Composition composition) { setCompositionFields(entryRecord, composition); } @Override public DataAccess getDataAccess() { return this; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy