org.hspconsortium.cwf.fhir.common.FhirUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.hspconsortium.cwf.fhir.common.FhirUtil.java

Source

/*
 * #%L
 * cwf-fhir-core
 * %%
 * Copyright (C) 2014 - 2016 Healthcare Services Platform Consortium
 * %%
 * 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
 * 
 *      http://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.
 * #L%
 */
package org.hspconsortium.cwf.fhir.common;

import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.carewebframework.common.DateUtil;
import org.hl7.fhir.dstu3.model.Address;
import org.hl7.fhir.dstu3.model.Address.AddressUse;
import org.hl7.fhir.dstu3.model.Annotation;
import org.hl7.fhir.dstu3.model.Bundle;
import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.dstu3.model.CodeableConcept;
import org.hl7.fhir.dstu3.model.Coding;
import org.hl7.fhir.dstu3.model.ContactPoint;
import org.hl7.fhir.dstu3.model.DateTimeType;
import org.hl7.fhir.dstu3.model.DateType;
import org.hl7.fhir.dstu3.model.HumanName;
import org.hl7.fhir.dstu3.model.HumanName.NameUse;
import org.hl7.fhir.dstu3.model.Identifier;
import org.hl7.fhir.dstu3.model.OperationOutcome;
import org.hl7.fhir.dstu3.model.OperationOutcome.IssueSeverity;
import org.hl7.fhir.dstu3.model.OperationOutcome.OperationOutcomeIssueComponent;
import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.dstu3.model.Period;
import org.hl7.fhir.dstu3.model.Quantity;
import org.hl7.fhir.dstu3.model.Reference;
import org.hl7.fhir.dstu3.model.SimpleQuantity;
import org.hl7.fhir.dstu3.model.Timing;
import org.hl7.fhir.dstu3.model.Timing.TimingRepeatComponent;
import org.hl7.fhir.dstu3.model.Timing.UnitsOfTime;
import org.hl7.fhir.instance.model.api.IBaseCoding;
import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;

import ca.uhn.fhir.rest.api.MethodOutcome;

/**
 * Domain object utility methods.
 */
public class FhirUtil {

    public static class OperationOutcomeException extends RuntimeException {

        private static final long serialVersionUID = 1L;

        private final OperationOutcome operationOutcome;

        private final IssueSeverity severity;

        private OperationOutcomeException(String message, IssueSeverity severity,
                OperationOutcome operationOutcome) {
            super(message);
            this.severity = severity;
            this.operationOutcome = operationOutcome;
        }

        public OperationOutcome getOperationOutcome() {
            return operationOutcome;
        }

        public IssueSeverity getSeverity() {
            return severity;
        }
    }

    public static IHumanNameParser defaultHumanNameParser = new HumanNameParser();

    /**
     * Adds a tag to a resource if not already present.
     * 
     * @param tag Tag to add.
     * @param resource Resource to receive tag.
     * @return True if the tag was added.
     */
    public static boolean addTag(IBaseCoding tag, IBaseResource resource) {
        boolean exists = resource.getMeta().getTag(tag.getSystem(), tag.getCode()) != null;

        if (!exists) {
            IBaseCoding newTag = resource.getMeta().addTag();
            newTag.setCode(tag.getCode());
            newTag.setSystem(tag.getSystem());
            newTag.setDisplay(tag.getDisplay());
        }

        return !exists;
    }

    /**
     * Returns the first resource tag matching the specified system.
     * 
     * @param resource The resource.
     * @param system The system.
     * @return The first matching tag or null if none found.
     */
    public static IBaseCoding getTagBySystem(IBaseResource resource, String system) {
        for (IBaseCoding coding : resource.getMeta().getTag()) {
            if (system.equals(coding.getSystem())) {
                return coding;
            }
        }

        return null;
    }

    /**
     * Returns all resource tags belonging to the specified system.
     * 
     * @param resource The resource.
     * @param system The system.
     * @return A list of matching tags; never null;
     */
    public static List<IBaseCoding> getTagsBySystem(IBaseResource resource, String system) {
        List<IBaseCoding> result = new ArrayList<>();

        for (IBaseCoding coding : resource.getMeta().getTag()) {
            if (system.equals(coding.getSystem())) {
                result.add(coding);
            }
        }

        return result;
    }

    /**
     * Performs an equality check on two resources using their id's.
     * 
     * @param res1 The first resource.
     * @param res2 The second resource.
     * @return True if the two resources have equal id's.
     */
    public static <T extends IBaseResource> boolean areEqual(T res1, T res2) {
        return areEqual(res1, res2, false);
    }

    /**
     * Performs an equality check on two resources using their id's.
     * 
     * @param res1 The first resource.
     * @param res2 The second resource.
     * @param ignoreVersion If true, ignore any version qualifiers in the comparison.
     * @return True if the two resources have equal id's.
     */
    public static <T extends IBaseResource> boolean areEqual(T res1, T res2, boolean ignoreVersion) {
        if (res1 == null || res2 == null) {
            return false;
        }

        return res1 == res2 || getIdAsString(res1, ignoreVersion).equals(getIdAsString(res2, ignoreVersion));
    }

    /**
     * Performs an equality check on two references using their id's.
     * 
     * @param ref1 The first resource.
     * @param ref2 The second resource.
     * @return True if the two references have equal id's.
     */
    public static <T extends Reference> boolean areEqual(T ref1, T ref2) {
        return areEqual(ref1, ref2, false);
    }

    /**
     * Performs an equality check on two resources using their id's.
     * 
     * @param ref1 The first resource.
     * @param ref2 The second resource.
     * @param ignoreVersion If true, ignore any version qualifiers in the comparison.
     * @return True if the two resources have equal id's.
     */
    public static <T extends Reference> boolean areEqual(T ref1, T ref2, boolean ignoreVersion) {
        if (ref1 == null || ref2 == null) {
            return false;
        }

        return ref1 == ref2 || getIdAsString(ref1, ignoreVersion).equals(getIdAsString(ref2, ignoreVersion));
    }

    /**
     * Performs an equality check between a resource and a reference using their id's.
     * 
     * @param res The resource.
     * @param ref The reference.
     * @return True if the two inputs have equal id's.
     */
    public static <T extends IBaseResource, R extends Reference> boolean areEqual(T res, R ref) {
        return areEqual(res, ref, false);
    }

    /**
     * Performs an equality check between a resource and a reference using their id's.
     * 
     * @param res The resource.
     * @param ref The reference.
     * @param ignoreVersion If true, ignore any version qualifiers in the comparison.
     * @return True if the two inputs have equal id's.
     */
    public static <T extends IBaseResource, R extends Reference> boolean areEqual(T res, R ref,
            boolean ignoreVersion) {
        if (res == null || ref == null) {
            return false;
        }

        IBaseResource res2 = ref.getResource();

        if (res2 != null) {
            return areEqual(res, res2, ignoreVersion);
        }

        return getIdAsString(ref, ignoreVersion).equals(getIdAsString(res, ignoreVersion));
    }

    /**
     * Checks the response from a server request to determine if it is an OperationOutcome with a
     * severity of ERROR or FATAL. If so, it will throw a runtime exception with the diagnostics of
     * the issue(s).
     * 
     * @param resource The resource returned by a server request.
     * @return Returns true if the resource was an OperationOutcome with no critical issues.
     * @throws OperationOutcomeException Exception if severity was ERROR or FATAL.
     */
    public static boolean checkOutcome(IBaseResource resource) {
        if (resource instanceof OperationOutcome) {
            OperationOutcome outcome = (OperationOutcome) resource;
            IssueSeverity severity = IssueSeverity.NULL;

            if (outcome.hasIssue()) {
                StringBuilder sb = new StringBuilder();

                for (OperationOutcomeIssueComponent issue : outcome.getIssue()) {
                    IssueSeverity theSeverity = issue.getSeverity();

                    if (theSeverity == IssueSeverity.ERROR || theSeverity == IssueSeverity.FATAL) {
                        sb.append(issue.getDiagnostics()).append(" (").append(theSeverity.getDisplay())
                                .append(")\n");
                        severity = theSeverity.ordinal() < severity.ordinal() ? theSeverity : severity;
                    }
                }

                if (sb.length() != 0) {
                    throw new OperationOutcomeException(sb.toString(), severity, outcome);
                }

            }

            return true;
        }

        return false;
    }

    /**
     * Returns true if the resource is assignment-compatible with one of the classes list.
     * 
     * @param classes List of classes to check.
     * @param resource The resource to test.
     * @return True if the resource is assignment-compatible with one of the classes in the list.
     */
    private static <T extends IBaseResource> boolean classMatches(List<Class<T>> classes, IBaseResource resource) {
        for (Class<T> clazz : classes) {
            if (clazz.isInstance(resource)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Concatenates a path fragment to a root path. Ensures that a single "/" character separates
     * the two parts.
     * 
     * @param root The root path.
     * @param fragment The path fragment.
     * @return The concatenated result.
     */
    public static String concatPath(String root, String fragment) {
        while (root.endsWith("/")) {
            root = root.substring(0, root.length() - 1);
        }

        while (fragment.startsWith("/")) {
            fragment = fragment.substring(1);
        }

        return root + "/" + fragment;
    }

    /**
     * Convert a string-based time unit to the corresponding enum.
     * 
     * @param timeUnit The time unit.
     * @return The corresponding enumeration value.
     */
    public static UnitsOfTime convertTimeUnitToEnum(String timeUnit) {
        try {
            return UnitsOfTime.valueOf(timeUnit.toUpperCase());
        } catch (Exception e) {
            throw new IllegalArgumentException("Unknown time unit " + timeUnit);
        }
    }

    /**
     * Convenience method that creates a CodeableConcept with a single coding.
     * 
     * @param system The coding system.
     * @param code The code.
     * @param displayName The concept's display name.
     * @return A CodeableConcept instance.
     */
    public static CodeableConcept createCodeableConcept(String system, String code, String displayName) {
        CodeableConcept codeableConcept = new CodeableConcept();
        Coding coding = new Coding(system, code, displayName);
        codeableConcept.addCoding(coding);
        return codeableConcept;
    }

    public static Identifier createIdentifier(String system, String value) {
        Identifier identifier = new Identifier();
        identifier.setSystem(system);
        identifier.setValue(value);
        return identifier;
    }

    /**
     * Creates a period object from a start and end date.
     * 
     * @param startDate The starting date.
     * @param endDate The ending date.
     * @return A period object, or null if both dates are null.
     */
    public static Period createPeriod(Date startDate, Date endDate) {
        Period period = null;

        if (startDate != null) {
            period = new Period();
            period.setStart(startDate);
        }

        if (endDate != null) {
            (period == null ? period = new Period() : period).setEnd(endDate);
        }

        return period;
    }

    /**
     * Method returns true if two quantities are equal. Compares two quantities by comparing their
     * values and their units. TODO Do a comparator instead
     * 
     * @param qty1 The first quantity
     * @param qty2 The second quantity
     * @return True if the two quantities are equal.
     */
    public static boolean equalQuantities(Quantity qty1, Quantity qty2) {
        if (qty1 == null || qty2 == null || qty1.getUnit() == null || qty2.getUnit() == null
                || qty1.getValue() == null || qty2.getValue() == null) {
            return false;
        } else if (qty1 == qty2) {
            return true;
        } else if ((qty1.getValue().compareTo(qty2.getValue()) == 0) && qty1.getUnit().equals(qty2.getUnit())) {
            // TODO: Fix floating compares within some margin.
            return true;
        } else {
            // TODO Flawed because no unit conversion done here. I am leaving this for a good utility.
            return false;
        }
    }

    /**
     * Formats a name using the active parser.
     * 
     * @param name HumanName instance.
     * @return Formatted name.
     */
    public static String formatName(HumanName name) {
        return name == null ? "" : defaultHumanNameParser.toString(name);
    }

    /**
     * Format the "usual" name.
     * 
     * @param names List of names
     * @return A formatted name.
     */
    public static String formatName(List<HumanName> names) {
        return formatName(names, NameUse.USUAL, null);
    }

    /**
     * Format a name of the specified use category.
     * 
     * @param names List of names
     * @param uses Use categories (use categories to search).
     * @return A formatted name.
     */
    public static String formatName(List<HumanName> names, NameUse... uses) {
        return formatName(getName(names, uses));
    }

    /**
     * Returns an address of the desired use category from a list.
     * 
     * @param list List of addresses to consider.
     * @param uses One or more use categories. These are searched in order until one is found. A
     *            null value matches any use category.
     * @return An address with a matching use category, or null if none found.
     */
    public static Address getAddress(List<Address> list, AddressUse... uses) {
        for (AddressUse use : uses) {
            for (Address address : list) {
                if (use == null || use.equals(address.getUse())) {
                    return address;
                }
            }
        }

        return null;
    }

    /**
     * Returns a list of addresses from a resource if one exists.
     * 
     * @param resource Resource of interest.
     * @return List of addresses associated with resource or null if none.
     */
    public static List<Address> getAddresses(IBaseResource resource) {
        return getListProperty(resource, "address", Address.class);
    }

    /**
     * Returns a coding of the desired system from a list.
     * 
     * @param list List of codings to consider.
     * @param systems One or more systems to consider. These are searched in order until one is
     *            found. A null value matches any system.
     * @return An coding with a matching system, or null if none found.
     */
    public static Coding getCoding(List<Coding> list, String... systems) {
        for (String system : systems) {
            for (Coding coding : list) {
                if (system == null || system.equals(coding.getSystem())) {
                    return coding;
                }
            }
        }

        return null;
    }

    /**
     * Returns an contact of the desired type category from a list.
     * 
     * @param list List of contacts to consider.
     * @param type Contact type to find (e.g., "home:phone").
     * @return A contact with a matching type, or null if none found.
     */
    public static ContactPoint getContact(List<ContactPoint> list, String type) {
        String[] pcs = type.split(":", 2);

        for (ContactPoint contact : list) {
            if (pcs[0].equals(contact.getUse()) && pcs[1].equals(contact.getSystem())) {
                return contact;
            }
        }

        return null;
    }

    public static String getDisplayValue(Annotation value) {
        return value.getText();
    }

    /**
     * Returns a displayable value for a codeable concept.
     * 
     * @param value Codeable concept.
     * @return Displayable value.
     */
    public static String getDisplayValue(CodeableConcept value) {
        Coding coding = FhirUtil.getFirst(value.getCoding());
        String result = coding == null ? "" : coding.getDisplay();
        return result == null ? coding.getCode() : result;
    }

    public static String getDisplayValue(DateTimeType value) {
        return DateUtil.formatDate(value.getValue());
    }

    public static String getDisplayValue(DateType value) {
        return DateUtil.formatDate(value.getValue());
    }

    public static String getDisplayValue(Period value) {
        Date start = value.getStart();
        Date end = value.getEnd();
        String result = "";

        if (start != null) {
            result = DateUtil.formatDate(start);

            if (start.equals(end)) {
                end = null;
            }
        }

        if (end != null) {
            result += (result.isEmpty() ? "" : " - ") + DateUtil.formatDate(end);
        }

        return result;
    }

    public static String getDisplayValue(Quantity value) {
        String units = value.getUnit();
        return value.getValue().toString() + (units == null ? "" : " " + units);
    }

    public static String getDisplayValue(Reference value) {
        return value.getDisplay();
    }

    public static String getDisplayValue(SimpleQuantity value) {
        String unit = value.hasUnit() ? " " + value.getUnit() : "";
        return value.getValue().toPlainString() + unit;
    }

    public static String getDisplayValue(Timing value) {
        StringBuilder sb = new StringBuilder(getDisplayValueForType(value.getCode())).append(" ");
        TimingRepeatComponent repeat = value.getRepeat();

        if (!repeat.isEmpty()) {
            // TODO: finish
        }

        if (!value.getEvent().isEmpty()) {
            sb.append(" at ").append(getDisplayValueForTypes(value.getEvent(), ", "));
        }

        return sb.toString();
    }

    /**
     * Delegates to the getDisplayValue function for the runtime type of value, if available.
     * Otherwise, calls toString() on the value.
     * 
     * @param value Value to format for display.
     * @return The formatted value.
     */
    public static String getDisplayValueForType(IBaseDatatype value) {
        if (value == null || value.isEmpty()) {
            return "";
        }

        try {
            Method method = MethodUtils.getAccessibleMethod(FhirUtil.class, "getDisplayValue", value.getClass());
            return method == null ? value.toString() : (String) method.invoke(null, value);
        } catch (Exception e) {
            return value.toString();
        }
    }

    /**
     * Invokes getDisplayValueForType on each list element, using the specified delimiter to
     * separate results.
     * 
     * @param values Values to format for display.
     * @param delimiter Delimiter to separate multiple values.
     * @return The formatted values.
     */
    public static String getDisplayValueForTypes(List<? extends IBaseDatatype> values, String delimiter) {
        StringBuilder sb = new StringBuilder();

        for (IBaseDatatype value : values) {
            String result = getDisplayValueForType(value);

            if (!result.isEmpty()) {
                sb.append(sb.length() == 0 ? "" : delimiter).append(result);
            }
        }

        return sb.toString();
    }

    /**
     * Extracts resources of the specified class from a bundle.
     * 
     * @param bundle The bundle.
     * @param clazz Class of resource to extract.
     * @return The list of extracted resources.
     */
    @SuppressWarnings("unchecked")
    public static <T extends IBaseResource> List<T> getEntries(Bundle bundle, Class<T> clazz) {
        return (List<T>) getEntries(bundle, Collections.singletonList(clazz), null);
    }

    /**
     * Extracts resources from a bundle according to the inclusion and exclusion criteria.
     * 
     * @param bundle The bundle.
     * @param inclusions List of resource classes to extract. May be null to indicate all classes
     *            should be extracted.
     * @param exclusions List of resource classes to be excluded. May be null to indicate no classes
     *            should be excluded. Exclusions take precedence over inclusions.
     * @return The list of extracted resources.
     */
    public static <T extends IBaseResource> List<IBaseResource> getEntries(Bundle bundle, List<Class<T>> inclusions,
            List<Class<T>> exclusions) {
        List<IBaseResource> entries = new ArrayList<>();

        if (bundle != null) {
            for (BundleEntryComponent entry : bundle.getEntry()) {
                IBaseResource resource = entry.getResource();
                boolean exclude = exclusions != null && classMatches(exclusions, resource);
                boolean include = !exclude && (inclusions == null || classMatches(inclusions, resource));

                if (include) {
                    entries.add(resource);
                }
            }
        }

        return entries;
    }

    /**
     * Returns the first element in a list, or null if there is none.
     * 
     * @param list A list.
     * @return The first list element, or null if none.
     */
    public static <T> T getFirst(List<T> list) {
        return list == null || list.isEmpty() ? null : list.get(0);
    }

    /**
     * Attempts to find the frequency code for the proposed timing. For instance Frequency = 1 in
     * Duration of 24h is 'QD' TODO Handle hard coding used for initial wiring
     * 
     * @param repeat The timing.
     * @return Frequency code corresponding to timing.
     */
    public static Coding getFrequencyFromRepeat(TimingRepeatComponent repeat) {
        Coding frequency = new Coding();
        if (repeat != null && repeat.getPeriod() != null) {
            frequency.setSystem(FhirTerminology.SYS_COGMED);
            if (repeat.getFrequency() == 1 && repeat.getPeriod().intValue() == 24) {
                frequency.setCode("1");
                frequency.setDisplay("QD");
            } else if ((repeat.getFrequency() == 1 && repeat.getPeriod().intValue() == 8)
                    || (repeat.getFrequency() == 3 && repeat.getPeriod().intValue() == 24)) {
                frequency.setCode("2");
                frequency.setDisplay("Q8H");
            }
        }
        return frequency;
    }

    /**
     * Returns the string representation of the id.
     * 
     * @param id The id.
     * @param stripVersion If true and the id has a version qualifier, remove it.
     * @return The string representation of the id.
     */
    public static String getIdAsString(IIdType id, boolean stripVersion) {
        String result = id == null ? null : id.getValueAsString();
        return result == null ? "" : stripVersion && id.hasVersionIdPart() ? stripVersion(result) : result;
    }

    /**
     * Returns the string representation of the resource's id.
     * 
     * @param resource The resource.
     * @param stripVersion If true and the id has a version qualifier, remove it.
     * @return The string representation of the resource's id.
     */
    public static <T extends IBaseResource> String getIdAsString(T resource, boolean stripVersion) {
        return getIdAsString(resource.getIdElement(), stripVersion);
    }

    /**
     * Returns the string representation of the reference's resource id.
     * 
     * @param reference The reference.
     * @param stripVersion If true and the id has a version qualifier, remove it.
     * @return The string representation of the id.
     */
    public static String getIdAsString(Reference reference, boolean stripVersion) {
        IBaseResource res = reference == null ? null : reference.getResource();

        if (res != null) {
            return getIdAsString(res, stripVersion);
        }
        String result = reference == null ? null : reference.getReference();
        return result == null ? "" : stripVersion ? stripVersion(result) : result;
    }

    /**
     * Returns the first identifier from the list that matches one of the specified types. A search
     * is performed for each specified type, returning when a match is found.
     * 
     * @param list List of identifiers to consider.
     * @param types Coding types to be matched.
     * @return A matching identifier, or null if not found.
     */
    public static Identifier getIdentifier(List<Identifier> list, Coding... types) {
        for (Coding type : types) {
            for (Identifier id : list) {
                for (Coding coding : id.getType().getCoding()) {
                    if (coding.getSystem().equals(type.getSystem()) && coding.getCode().equals(type.getCode())) {
                        return id;
                    }
                }
            }
        }

        return null;
    }

    /**
     * Returns identifiers for the given resource, if any.
     * 
     * @param resource Resource whose identifiers are sought.
     * @return List of associated identifier, or null if the resource doesn't support identifiers.
     */
    @SuppressWarnings("unchecked")
    public static List<Identifier> getIdentifiers(IBaseResource resource) {
        return getProperty(resource, "getIdentifier", List.class);
    }

    /**
     * Returns the last element in a list, or null if there is none.
     * 
     * @param list A list.
     * @return The last list element, or null if none.
     */
    public static <T> T getLast(List<T> list) {
        return list == null || list.isEmpty() ? null : list.get(list.size() - 1);
    }

    /**
     * Returns a patient's MRN. (What types should be explicitly considered?)
     * 
     * @param patient Patient
     * @return MRN identifier
     */
    public static Identifier getMRN(Patient patient) {
        return patient == null ? null : getIdentifier(patient.getIdentifier(), FhirTerminology.CODING_MRN);
    }

    /**
     * Returns a patient's MRN. (What labels should be explicitly considered?)
     * 
     * @param patient Patient
     * @return MRN as a string.
     */
    public static String getMRNString(Patient patient) {
        Identifier identifier = getMRN(patient);
        return identifier == null ? "" : identifier.getValue();
    }

    /**
     * Returns a name of the desired use category from a list.
     * 
     * @param list List of names to consider.
     * @param uses One or more use categories. These are searched in order until one is found. A
     *            null value matches any use category.
     * @return A name with a matching use category, or null if none found.
     */
    public static HumanName getName(List<HumanName> list, NameUse... uses) {
        for (NameUse use : uses) {
            for (HumanName name : list) {
                if (use == null || use.equals(name.getUse())) {
                    return name;
                }
            }
        }

        return null;
    }

    /**
     * Returns a list of names from a resource if one exists.
     * 
     * @param resource Resource of interest.
     * @return List of names associated with resource or null if none.
     */
    public static List<HumanName> getNames(IBaseResource resource) {
        return getListProperty(resource, "name", HumanName.class);
    }

    /**
     * Returns the patient associated with a resource.
     * 
     * @param resource Resource whose associated patient is sought.
     * @return A patient resource.
     */
    public static Reference getPatient(IBaseResource resource) {
        Reference ref = getProperty(resource, "getPatient", Reference.class);
        ref = ref != null ? ref : getProperty(resource, "getSubject", Reference.class);
        return ref == null || !ref.hasReference() ? null
                : "Patient".equals(getResourceType(ref.getReferenceElement())) ? ref : null;
    }

    /**
     * Returns the value of a property from a resource base.
     * 
     * @param resource The resource containing the property.
     * @param getter The name of the getter method for the property.
     * @param expectedClass The expected class of the property value (null for any).
     * @return The value of the property. A null return value may mean the property does not exist
     *         or the property getter returned null. Will never throw an exception.
     */
    @SuppressWarnings("unchecked")
    private static <T> T getProperty(IBaseResource resource, String getter, Class<T> expectedClass) {
        Object result = null;

        try {
            result = MethodUtils.invokeMethod(resource, getter, (Object[]) null);
            result = result == null || expectedClass == null ? result
                    : expectedClass.isInstance(result) ? result : null;
        } catch (Exception e) {
        }

        return (T) result;
    }

    /**
     * Returns the value of a property that returns a list from a resource base.
     * 
     * @param resource The resource containing the property.
     * @param propertyName The name of the property.
     * @param itemClass The expected class of the list elements.
     * @return The value of the property. A null return value may mean the property does not exist
     *         or the property getter returned null. Will never throw an exception.
     */
    @SuppressWarnings("unchecked")
    public static <T> List<T> getListProperty(IBaseResource resource, String propertyName, Class<T> itemClass) {
        try {
            return (List<T>) PropertyUtils.getSimpleProperty(resource, propertyName);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * Method sets the FHIR repeat for the given frequency code
     * 
     * @param frequencyCode The frequency code.
     * @return The corresponding timing.
     */
    public static TimingRepeatComponent getRepeatFromFrequencyCode(String frequencyCode) {
        TimingRepeatComponent repeat = new TimingRepeatComponent();
        if (frequencyCode != null && frequencyCode.equals("QD")) {
            repeat.setFrequency(1);
            repeat.setPeriod(24);
        }
        if (frequencyCode != null && frequencyCode.equals("Q8H")) {
            repeat.setFrequency(1);
            repeat.setPeriod(8);
        }
        return repeat;
    }

    /**
     * Returns the base 64-encoded equivalent of a resource.
     * 
     * @param resourceName The resource name.
     * @return The base 64-encoded resource.
     */
    public static byte[] getResourceAndConvertToBase64(String resourceName) {
        return Base64.encodeBase64(getResourceAsByteArray(resourceName));
    }

    /**
     * Returns the resource as a byte array.
     * 
     * @param resourceName The resource name.
     * @return The resource as a byte array.
     */
    public static byte[] getResourceAsByteArray(String resourceName) {
        try (InputStream is = FhirUtil.class.getClassLoader().getResource(resourceName).openStream()) {
            return IOUtils.toByteArray(is);
        } catch (Exception e) {
            throw new RuntimeException("Error deserializing file " + resourceName, e);
        }
    }

    /**
     * Returns the resource ID relative path.
     * 
     * @param resource The resource.
     * @return The resource's relative path.
     */
    public static String getResourceIdPath(IBaseResource resource) {
        return getResourceIdPath(resource, true);
    }

    /**
     * Returns the resource ID relative path.
     * 
     * @param resource The resource.
     * @param stripVersion If true and the id has a version qualifier, remove it.
     * @return The resource's relative path.
     */
    public static String getResourceIdPath(IBaseResource resource, boolean stripVersion) {
        String id = resource.getIdElement().getResourceType() + "/" + resource.getIdElement().getIdPart();
        return stripVersion ? stripVersion(id) : id;
    }

    /**
     * Returns the resource type from a resource.
     * 
     * @param resource The resource.
     * @return The type of resource.
     */
    public static String getResourceType(IBaseResource resource) {
        return resource == null ? null : getResourceType(resource.getIdElement());
    }

    /**
     * Extracts a resource type from an id.
     * 
     * @param id Identifier.
     * @return The resource type.
     */
    public static String getResourceType(IIdType id) {
        return id == null || id.isEmpty() ? null : id.getResourceType();
    }

    /**
     * Casts an unspecified data type to a specific data type if possible.
     * 
     * @param value The value to cast.
     * @param clazz The type to cast to.
     * @return The value cast to the specified type, or null if not possible.
     */
    @SuppressWarnings("unchecked")
    public static <V, T extends V> T getTyped(V value, Class<T> clazz) {
        return clazz.isInstance(value) ? (T) value : null;
    }

    /**
     * Parses a name using the active parser.
     * 
     * @param name String form of name.
     * @return Parsed name.
     */
    public static HumanName parseName(String name) {
        return name == null ? null : defaultHumanNameParser.fromString(null, name);
    }

    /**
     * Processes a MethodOutcome from a create or update request. If the request returns an updated
     * version of the resource, that resource is returned. If the request returns a logical id, that
     * id is set in the original resource. If the request resulted in an error, a runtime exception
     * is thrown.
     * 
     * @param outcome The method outcome.
     * @param resource The resource upon which the method was performed.
     * @return If the method returned a new resource, that resource is returned. Otherwise, the
     *         original resource is returned, possibly with an updated logical id.
     */
    @SuppressWarnings("unchecked")
    public static <T extends IBaseResource> T processMethodOutcome(MethodOutcome outcome, T resource) {
        checkOutcome(outcome.getOperationOutcome());
        IIdType id = outcome.getId();
        IBaseResource newResource = outcome.getResource();

        if (id != null) {
            resource.setId(id);
        } else if (newResource != null && newResource.getClass() == resource.getClass()) {
            resource = (T) newResource;
        }

        return resource;
    }

    /**
     * Removes a tag from a resource if present.
     * 
     * @param tag Tag to remove.
     * @param resource Resource to containing tag.
     * @return True if the tag was removed.
     */
    public static boolean removeTag(IBaseCoding tag, IBaseResource resource) {
        IBaseCoding theTag = resource.getMeta().getTag(tag.getSystem(), tag.getCode());

        if (theTag != null) {
            resource.getMeta().getTag().remove(theTag);
            return true;
        }

        return false;
    }

    /**
     * Strips the version qualifier from an id, if present.
     * 
     * @param id The id.
     * @return The id without a version qualifier.
     */
    public static String stripVersion(String id) {
        int i = id.lastIndexOf("/_history");
        return i == -1 ? id : id.substring(0, i);
    }

    /**
     * Strips the version qualifier from a resource, if present.
     * 
     * @param resource The resource.
     * @return The input resource, possibly modified.
     */
    public static <T extends IBaseResource> T stripVersion(T resource) {
        IIdType id = resource.getIdElement();

        if (id.hasVersionIdPart()) {
            id.setValue(stripVersion(id.getValue()));
            resource.setId(id);
        }

        return resource;
    }

    /**
     * Converts a list of objects to a list of their string equivalents.
     * 
     * @param source The source list.
     * @return A list of string equivalents.
     */
    public static List<String> toStringList(List<?> source) {
        List<String> dest = new ArrayList<>(source.size());

        for (Object value : source) {
            dest.add(value.toString());
        }

        return dest;
    }

    /**
     * Enforce static class.
     */
    private FhirUtil() {
    }
}