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

com.imsweb.layout.hl7.NaaccrHl7Layout Maven / Gradle / Ivy

Go to download

Framework that allows defining file formats (layouts) and use them to read and write data files.

There is a newer version: 5.5
Show newest version
/*
 * Copyright (C) 2017 Information Management Services, Inc.
 */
package com.imsweb.layout.hl7;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.imsweb.layout.Field;
import com.imsweb.layout.Layout;
import com.imsweb.layout.LayoutFactory;
import com.imsweb.layout.LayoutInfo;
import com.imsweb.layout.LayoutInfoDiscoveryOptions;
import com.imsweb.layout.LayoutUtils;
import com.imsweb.layout.hl7.entity.Hl7Message;
import com.imsweb.layout.hl7.xml.Hl7ComponentXmlDto;
import com.imsweb.layout.hl7.xml.Hl7FieldXmlDto;
import com.imsweb.layout.hl7.xml.Hl7LayoutXmlDto;
import com.imsweb.layout.hl7.xml.Hl7SegmentXmlDto;
import com.imsweb.layout.hl7.xml.Hl7SubComponentXmlDto;

/**
 * Provides the functionality for reading/writing NAACCR HL7 messages.
 */
public class NaaccrHl7Layout implements Layout {

    /**
     * Layout ID
     */
    protected String _layoutId;

    /**
     * Layout name
     */
    protected String _layoutName;

    /**
     * Layout version
     */
    protected String _layoutVersion;

    /**
     * Layout description
     */
    protected String _layoutDesc;

    /**
     * The fields for this layout
     */
    protected List _fields = new ArrayList<>();

    /**
     * Cached fields by name
     */
    protected Map _cachedByName = new HashMap<>();

    /**
     * Constructor.
     * @param layoutId layout ID, cannot be null
     * @param layoutVersion layout version, cannot be null
     * @param loadFields if true, then load the fields
     */
    public NaaccrHl7Layout(String layoutId, String layoutVersion, boolean loadFields) {

        // optimization - if we don't need to load the fields, then don't load the XML at all!
        Hl7LayoutXmlDto layoutXmlDto = new Hl7LayoutXmlDto();
        layoutXmlDto.setId(layoutId);
        layoutXmlDto.setName(LayoutFactory.getAvailableInternalLayouts().get(layoutId));
        layoutXmlDto.setVersion(layoutVersion);
        layoutXmlDto.setDescription("NAACCR HL7 version " + layoutVersion);

        try {
            if (loadFields) {
                String xmlFilename = layoutId + "-layout.xml";
                Hl7LayoutXmlDto tmpXmlLayout = LayoutUtils.readHl7Layout(Thread.currentThread().getContextClassLoader().getResourceAsStream("layout/hl7/naaccr/" + xmlFilename));
                layoutXmlDto.setHl7Segments(tmpXmlLayout.getHl7Segments());
            }

            init(layoutXmlDto);
        }
        catch (IOException e) {
            throw new RuntimeException("Unable to instantiate NAACCR HL7 layout", e);
        }
    }

    /**
     * Constructor.
     * @param layoutUrl URL to the XML definition, cannot be null
     * @throws IOException if the XML definition is not valid
     */
    public NaaccrHl7Layout(URL layoutUrl) throws IOException {
        if (layoutUrl == null)
            throw new NullPointerException("Unable to create HL7 layout from null URL");

        try (InputStream is = layoutUrl.openStream()) {
            init(LayoutUtils.readHl7Layout(is));
        }
    }

    /**
     * Constructor.
     * @param layoutFile XML definition, cannot be null, must exist
     * @throws IOException if the XML definition is not valid
     */
    public NaaccrHl7Layout(File layoutFile) throws IOException {
        if (layoutFile == null)
            throw new NullPointerException("Unable to create HL7-layout from null file");
        if (!layoutFile.exists())
            throw new IOException("Unable to read from " + layoutFile.getPath());

        try (InputStream is = new FileInputStream(layoutFile)) {
            init(LayoutUtils.readHl7Layout(is));
        }
    }

    /**
     * Constructor.
     * @param layoutXmlDto java representation of the XML definition, cannot be null
     * @throws IOException if the XML definition is not valid
     */
    public NaaccrHl7Layout(Hl7LayoutXmlDto layoutXmlDto) throws IOException {
        if (layoutXmlDto == null)
            throw new NullPointerException("Unable to create HL7-layout from null XML object");

        init(layoutXmlDto);
    }

    // helper
    protected void init(Hl7LayoutXmlDto layoutXmlDto) throws IOException {

        _layoutId = layoutXmlDto.getId();
        _layoutName = layoutXmlDto.getName();
        _layoutVersion = layoutXmlDto.getVersion();
        _layoutDesc = layoutXmlDto.getDescription();

        _fields.clear();
        for (Hl7SegmentXmlDto segmentXmlDto : layoutXmlDto.getHl7Segments()) {
            for (Hl7FieldXmlDto fieldXmlDto : segmentXmlDto.getHl7Fields()) {
                NaaccrHl7Field field = createFieldFromXmlField(fieldXmlDto);
                List subFields = new ArrayList<>();
                for (Hl7ComponentXmlDto componentXmlDto : fieldXmlDto.getHl7Components()) {
                    NaaccrHl7Field subField = createFieldFromXmlComponent(componentXmlDto);
                    List subSubFields = new ArrayList<>();
                    for (Hl7SubComponentXmlDto subComponentXmlDto : componentXmlDto.getHl7SubComponents())
                        subSubFields.add(createFieldFromXmlSubComponent(subComponentXmlDto));
                    if (!subSubFields.isEmpty())
                        subField.setSubFields(subSubFields);
                    subFields.add(subField);
                }
                if (!subFields.isEmpty())
                    field.setSubFields(subFields);
                _fields.add(field);
                _cachedByName.put(field.getName(), field);
            }
        }
    }

    // helper
    protected NaaccrHl7Field createFieldFromXmlField(Hl7FieldXmlDto hl7FieldXmlDto) throws IOException {
        NaaccrHl7Field field = new NaaccrHl7Field();

        field.setName(hl7FieldXmlDto.getName());
        field.setIdentifier(hl7FieldXmlDto.getIdentifier());
        field.setLongLabel(hl7FieldXmlDto.getLongLabel());
        field.setType(hl7FieldXmlDto.getType());
        field.setMinOccurrence(hl7FieldXmlDto.getMinOccurrence());
        field.setMaxOccurrence(hl7FieldXmlDto.getMaxOccurrence());
        if (hl7FieldXmlDto.getMinOccurrence() > hl7FieldXmlDto.getMaxOccurrence())
            throw new IOException("Field " + field.getName() + " defines a minimum occurrence that is greater than the maximum occurrence");

        return field;
    }

    // helper
    protected NaaccrHl7Field createFieldFromXmlComponent(Hl7ComponentXmlDto hl7ComponentXmlDto) {
        NaaccrHl7Field field = new NaaccrHl7Field();

        field.setName(hl7ComponentXmlDto.getName());
        field.setIdentifier(hl7ComponentXmlDto.getIdentifier());
        field.setLongLabel(hl7ComponentXmlDto.getLongLabel());
        field.setType(hl7ComponentXmlDto.getType());

        return field;
    }

    // helper
    protected NaaccrHl7Field createFieldFromXmlSubComponent(Hl7SubComponentXmlDto hl7FSubComponentXmlDto) {
        NaaccrHl7Field field = new NaaccrHl7Field();

        field.setName(hl7FSubComponentXmlDto.getName());
        field.setIdentifier(hl7FSubComponentXmlDto.getIdentifier());
        field.setLongLabel(hl7FSubComponentXmlDto.getLongLabel());
        field.setType(hl7FSubComponentXmlDto.getType());

        return field;
    }

    @Override
    public String getLayoutId() {
        return _layoutId;
    }

    @Override
    public String getLayoutName() {
        return _layoutName;
    }

    @Override
    public String getLayoutVersion() {
        return _layoutVersion;
    }

    @Override
    public String getLayoutDescription() {
        return _layoutDesc;
    }

    @Override
    public Field getFieldByName(String name) {
        return _cachedByName.get(name);
    }

    @Override
    public Field getFieldByNaaccrItemNumber(Integer num) {
        return null;
    }

    @Override
    public List getAllFields() {
        return Collections.unmodifiableList(_fields);
    }

    @Override
    public String getFieldDocByName(String name) {
        return null;
    }

    @Override
    public String getFieldDocByNaaccrItemNumber(Integer num) {
        return null;
    }

    @Override
    public String getFieldDocDefaultCssStyle() {
        return null;
    }

    @Override
    public LayoutInfo buildFileInfo(File file, String zipEntryName, LayoutInfoDiscoveryOptions options) {
        LayoutInfo result = null;

        try (LineNumberReader reader = new LineNumberReader(new InputStreamReader(LayoutUtils.createInputStream(file, zipEntryName), StandardCharsets.UTF_8))) {
            String line = reader.readLine();
            while (line != null && reader.getLineNumber() < 10 && result == null) {
                if (line.startsWith("MSH") && _layoutVersion.equals(Hl7Utils.segmentFromString(new Hl7Message(), line).getField(12).getComponent(1).getValue())) {
                    result = new LayoutInfo();
                    result.setLayoutId(getLayoutId());
                    result.setLayoutName(getLayoutName());
                }
                line = reader.readLine();
            }
        }
        catch (IOException e) {
            // ignored, result will be null
        }

        return result;
    }

    /**
     * Reads the next message from the given reader; this method won't close the reader.
     */
    public Hl7Message readNextMessage(LineNumberReader reader) throws IOException {
        return fetchNextMessage(reader);
    }

    /**
     * Reads all the messages from the given file.
     */
    public List readAllMessages(File file) throws IOException {
        List result = new ArrayList<>();
        try (LineNumberReader reader = new LineNumberReader(new InputStreamReader(LayoutUtils.createInputStream(file), StandardCharsets.UTF_8))) {
            Hl7Message message = readNextMessage(reader);
            while (message != null) {
                result.add(message);
                message = readNextMessage(reader);
            }
        }
        return result;
    }

    /**
     * Writes the given message on the given writer; this method won't close the writer.
     */
    public void writeMessage(Writer writer, Hl7Message message) throws IOException {
        writer.write(Hl7Utils.messageToString(message));
        writer.write(System.lineSeparator());
    }

    /**
     * Writes the given messages in the given file.
     */
    public void writeMessages(File file, List messages) throws IOException {
        try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(LayoutUtils.createOutputStream(file), StandardCharsets.UTF_8))) {
            for (Hl7Message message : messages)
                writeMessage(writer, message);
        }
    }

    // helper
    protected Hl7Message fetchNextMessage(LineNumberReader reader) throws IOException {
        Hl7Message msg = null;

        String line = reader.readLine();

        // ignore the FHS and BHS line (those are transmission segments that we don't use)
        while (line != null && (line.startsWith("FHS") || line.startsWith("BHS")))
            line = reader.readLine();

        // MSH should immediately follow FHS or BHS otherwise it's bad format
        while (line != null && !line.startsWith("MSH"))
            line = reader.readLine();

        // if we found the header, create the message
        if (line != null) {
            msg = new Hl7Message();
            msg.setLineNumber(reader.getLineNumber());

            // then read the block of text
            while (line != null && !line.trim().isEmpty()) {
                if (line.length() > 3)
                    Hl7Utils.segmentFromString(msg, line);

                // peek for the next three bytes to determine if next line is a message starter
                reader.mark(8192); // this is the default buffer size for the BufferedReader
                char[] peek = new char[3];
                int n = reader.read(peek);
                reader.reset();
                if (n != -1 && "MSH".equals(String.valueOf(peek)))
                    break;

                line = reader.readLine();
            }
        }

        return msg;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy