com.apothesource.pillfill.service.drug.impl.DefaultDrugServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.apothesource.pillfill.service.drug.impl.DefaultDrugServiceImpl.java

Source

/*
 *
 *  * The MIT License
 *  *
 *  * Copyright {$YEAR} Apothesource, Inc.
 *  *
 *  * Permission is hereby granted, free of charge, to any person obtaining a copy
 *  * of this software and associated documentation files (the "Software"), to deal
 *  * in the Software without restriction, including without limitation the rights
 *  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  * copies of the Software, and to permit persons to whom the Software is
 *  * furnished to do so, subject to the following conditions:
 *  *
 *  * The above copyright notice and this permission notice shall be included in
 *  * all copies or substantial portions of the Software.
 *  *
 *  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *  * THE SOFTWARE.
 *
 */
package com.apothesource.pillfill.service.drug.impl;

import com.apothesource.pillfill.datamodel.DrugInformation;
import com.apothesource.pillfill.datamodel.PrescriptionType;
import com.apothesource.pillfill.datamodel.SplSearchResultEntry;
import com.apothesource.pillfill.datamodel.ndfrt.Concept;
import com.apothesource.pillfill.datamodel.ndfrt.FullConcept;
import com.apothesource.pillfill.datamodel.ndfrt.Property;
import com.apothesource.pillfill.datamodel.ndfrt.Role;
import com.apothesource.pillfill.datamodel.spl.Ingredient;
import com.apothesource.pillfill.datamodel.spl.Pkg;
import com.apothesource.pillfill.datamodel.spl.Product;
import com.apothesource.pillfill.datamodel.spl.SplEntry;
import com.apothesource.pillfill.datamodel.spl.SplInformation;
import com.apothesource.pillfill.network.PFNetworkManager;
import com.apothesource.pillfill.service.drug.DrugService;
import com.apothesource.pillfill.service.PFServiceEndpoints;
import com.apothesource.pillfill.utilites.ResourceUtil;
import static com.apothesource.pillfill.utilites.ReactiveUtils.subscribeIoObserveImmediate;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;

import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import rx.Observable;
import timber.log.Timber;

public class DefaultDrugServiceImpl implements DrugService {
    private static final String URL_RXNORM_BRAND_NAME_BY_NDC = "http://rxnav.nlm.nih.gov/REST/rxcui.json?idtype=NDC&id=%s";
    private static Gson gson = new GsonBuilder().disableHtmlEscaping().setDateFormat("yyyy-MM-dd").create();
    private final XPathExpression XPATH_GET_DRUG_BN;
    private final Properties urlToTypeMapping;

    public Comparator<SplSearchResultEntry> scoreComparator = (lhs, rhs) -> {
        try {
            float lhsScore = Float.parseFloat(lhs.getScore());
            float rhsScore = Float.parseFloat(rhs.getScore());
            if (lhsScore == rhsScore)
                return 0;
            return (lhsScore - rhsScore) < 0 ? -1 : 1;
        } catch (Exception e) {
            return 0;
        }
    };

    public DefaultDrugServiceImpl() {
        XPath path = XPathFactory.newInstance().newXPath();
        urlToTypeMapping = ResourceUtil.getInstance().getMappingResources();
        try {
            XPATH_GET_DRUG_BN = path.compile("//conceptGroup[tty[text()='SBD']]/conceptProperties/synonym/text()");
        } catch (XPathExpressionException e) {
            throw new RuntimeException("Could not compile BrandName xpath", e);
        }

    }

    /**
     *
     *
     *
     *
     *
     *
     *
     *
     * Observable Network Call-Dependent Methods
     *
     *
     *
     *
     *
     *
     *
     *
     */

    /**
     * <a href="http://rxnav.nlm.nih.gov/RxNormAPIs.html#">NIH Service</a>: Get avaliable RxNorm IDs associated with an 11 digit National Drug Code (NDC).
     *
     * @param ndc The 11-digit NDC without dashes or spaces
     * @return A list of RxNorm IDs associated with this NDC (should normally return 0 or 1)
     */
    @Override
    public Observable<String> getRxNormIdForDrugNdc(String ndc) {
        return subscribeIoObserveImmediate(subscriber -> {
            String urlStr = String.format(URL_RXNORM_BRAND_NAME_BY_NDC, ndc);
            try {
                String responseStr = PFNetworkManager.doPinnedGetForUrl(urlStr);
                JsonParser parser = new JsonParser();
                JsonObject response = parser.parse(responseStr).getAsJsonObject().get("idGroup").getAsJsonObject();
                if (response.has("rxnormId")) {
                    JsonArray array = response.getAsJsonArray("rxnormId");
                    for (JsonElement e : array) {
                        subscriber.onNext(e.getAsString());
                    }
                    subscriber.onCompleted();
                } else {
                    Timber.e("No rxnormIds found for NDC: %s", ndc);
                    subscriber.onError(new RuntimeException("Could not find NDC->RxNorm for NDC."));
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        });

    }

    /**
     * Retrieve {@link FullConcept} for for the provided FDA <code><a href="http://www.fda.gov/ForIndustry/DataStandards/SubstanceRegistrationSystem-UniqueIngredientIdentifierUNII/">UNique Ingredient Identifier</a></code>.
     * @param uniis A list of the Drug UNIIs to be retrived
     * @return An observable that emits a {@link FullConcept} for each related concept found in the National Drug File (NDF)
     */
    @Override
    public Observable<FullConcept> getNdfrtConceptsByUnii(String... uniis) {
        return getConceptsByIds(uniis);
    }

    public Observable<FullConcept> getConceptsByIds(String... ids) {
        return getDrugInformation(ids).filter(drugInfo -> !drugInfo.concepts.isEmpty())
                .flatMap(drugInfo -> Observable.from(drugInfo.concepts));
    }

    /**
     * Retrieve {@link SplInformation} for the provided SPL (FDA's Standard Packaging Label) Drug IDs. Each {@link SplInformation} instance represents a specific product & package on the US Drug Market (both OTC & RX). The class includes basic patient-focused drug information, ingredients, strengths, etc.
     * @param ids The list of SPL IDs to lookup
     * @return An observable that emits a {@link SplInformation} instance for each drug
     * @see SplEntry
     */
    @Override
    public Observable<SplEntry> getSplInformation(String... ids) {
        return getDrugInformation(ids).filter(drugInfo -> !drugInfo.products.isEmpty())
                .flatMap(drugInfo -> Observable.from(drugInfo.products));

    }

    /**
     * Retrieve {@link FullConcept} for for the provided National Drug File (NDF) Concept IDs (NUIs). IDs should be in standard NDF Concept ID format of 'N' followed by 10 digits (i.e. (N[\d]{10}) - e.g. 'N0000155155')
     * @param nuis A list of the NUIs to be retrived
     * @return An observable that emits {@link FullConcept}s based on the provided NUI list
     */
    @Override
    public Observable<FullConcept> getNdfrtInformation(String... nuis) {
        return getConceptsByIds(nuis);
    }

    /**
     * An experimental method to enable a list of generic concepts to be retrieved without a method-specific implementation. Use with caution.
     * @param type The {@link Type} of class to be returned. Must be mapped in the <code>/src/main/resources/PFResourceMapping.properties</code> file or a RuntimeException will occur.
     * @param ids The list of IDs to retrieve
     * @return An observable that emits instances of the provided type based on the requested IDs
     */
    protected <T> Observable<T> getConceptList(Class<T> type, String... ids) {
        if (ids == null || ids.length == 0)
            return Observable.empty();
        String urlTemplate = getUrlForType(type);
        String url = String.format(urlTemplate, Joiner.on(",").join(ids));
        String typeName = type.getSimpleName();
        Timber.d("Requesting %s list from URL: %s", typeName, url);
        return subscribeIoObserveImmediate(subscriber -> {
            try {
                String listJson = PFNetworkManager.doPinnedGetForUrl(url);
                JsonParser parser = new JsonParser();
                JsonArray array = parser.parse(listJson).getAsJsonArray();
                for (JsonElement elem : array) {
                    subscriber.onNext(gson.fromJson(elem, type));
                }
                subscriber.onCompleted();
            } catch (IOException e) {
                Timber.e("Error retrieving %s list with ids %s - %s", typeName, ids, e.getMessage());
                subscriber.onError(e);
            }
        });
    }

    private String getUrlForType(Class t) {
        String[] components = t.getSimpleName().split("[\\.]");
        String simpleName = components[components.length - 1].replaceAll("[^\\w]", "");
        return urlToTypeMapping.getProperty(simpleName);
    }

    /**
     * Get a list of brand names associated with the provided drug identified by its RxNorm ID (CUI).
     * @param rxnormId The RxNormID for the generic drug
     * @return A list of brand names associated with the generic drug
     */
    @Override
    public Observable<String> getBrandNamesForDrug(String rxnormId) {
        if (rxnormId == null)
            return Observable.empty();

        return subscribeIoObserveImmediate(subscriber -> {
            try {
                List<String> brandNames = loadBrandNameDrug(rxnormId);
                if (!brandNames.isEmpty())
                    Observable.from(brandNames).forEach(subscriber::onNext);
                subscriber.onCompleted();
            } catch (Exception e) {
                Timber.e(e, "Error accessing brand name for rxNormId: %s", rxnormId);
                subscriber.onError(e);
            }
        });
    }

    @Override
    public Observable<DrugInformation> getDrugInformation(String... ids) {
        if (ids == null || ids.length == 0)
            return Observable.empty();
        String idList = Joiner.on(",").join(ids);
        final String url = String.format(PFServiceEndpoints.DRUG_INFO_URL, idList);
        return subscribeIoObserveImmediate(subscriber -> {
            try {
                String response = PFNetworkManager.doPinnedGetForUrl(url);
                List<DrugInformation> drugInformation = gson.fromJson(response, DRUGINFO_LIST_TYPE);
                if (!drugInformation.isEmpty()) {
                    Observable.from(drugInformation).forEach(subscriber::onNext);
                }
                subscriber.onCompleted();
            } catch (IOException e) {
                Timber.e("Couldn't get DrugInformation concepts for IDs: %s", ids.toString());
                subscriber.onError(e);
            }
        });
    }

    private List<String> loadBrandNameDrug(final String rxnormId) throws IOException {
        final String rxNormUrlTemplate = "http://rxnav.nlm.nih.gov/REST/rxcui/%s/allrelated";
        final String urlString = String.format(rxNormUrlTemplate, rxnormId);
        String response = PFNetworkManager.doPinnedGetForUrl(urlString);
        try {
            NodeList nl = (NodeList) XPATH_GET_DRUG_BN.evaluate(new InputSource(new StringReader(response)),
                    XPathConstants.NODESET);
            ArrayList<String> retList = new ArrayList<>();
            for (int i = 0; i < nl.getLength(); i++) {
                retList.add(nl.item(i).getNodeValue());
            }
            return retList;
        } catch (XPathExpressionException e) {
            Timber.e(e, "Error processing response: %s", response);
        }
        return Collections.emptyList();
    }

    /**
     *
     *
     *
     *
     *
     *
     * Static Utility Methods.
     *
     *
     *
     *
     *
     *
     **/

    public static Optional<Product> getProductByNdc(SplEntry entry, String ndc) {
        for (Product p : entry.getProducts().getProduct()) {
            for (Pkg pkg : p.getPkg()) {
                if (ndc != null && pkg.getNdc() != null && pkg.getNdc().startsWith(ndc))
                    return Optional.of(p);
            }
        }
        return Optional.absent();
    }

    public static ArrayList<Concept> getIngredients(FullConcept fc) {
        return getRolesWithName("has_Ingredient", fc);
    }

    public static ArrayList<Concept> getTreatedConditions(FullConcept fc) {
        return getRolesWithName("may_treat", fc);
    }

    public static ArrayList<String> getUnii(FullConcept fc) {
        return getPropertiesWithName("FDA_UNII", fc);
    }

    public static ArrayList<String> getProperty(String propertyName, FullConcept fc) {
        return getPropertiesWithName(propertyName, fc);
    }

    public static String getDefinition(FullConcept fc) {
        ArrayList<String> list = getPropertiesWithName("MeSH_Definition", fc);
        if (list.isEmpty()) {
            return null;
        } else {
            return list.get(0);
        }
    }

    public static ArrayList<Concept> getContraindications(FullConcept fc) {
        return getRolesWithName("CI_with", fc);
    }

    private static ArrayList<Concept> getRolesWithName(String name, FullConcept fc) {
        ArrayList<Concept> retList = new ArrayList<>();
        for (Role role : fc.getGroupRoles().getRole()) {
            if (role.getRoleName().equalsIgnoreCase(name)) {
                retList.add(role.getConcept());
            }
        }
        return retList;
    }

    private static ArrayList<String> getPropertiesWithName(String name, FullConcept fc) {
        ArrayList<String> retList = new ArrayList<>();
        if (fc == null)
            return retList;
        if (fc.getGroupProperties() == null)
            return retList;
        if (fc.getGroupProperties().getProperty() == null)
            return retList;
        for (Property property : fc.getGroupProperties().getProperty()) {
            if (property.getPropertyName().equalsIgnoreCase(name)) {
                retList.add(property.getPropertyValue());
            }
        }
        return retList;
    }

    public static List<String> getUniisFromSpl(SplEntry splEntry) {
        HashSet<String> uniis = new HashSet<>();
        if (splEntry == null || splEntry.getProducts() == null || splEntry.getProducts().getProduct() == null)
            return null;
        for (Product p : splEntry.getProducts().getProduct()) {
            if (p.getIngredient() == null) {
                return null;
            }
            for (Ingredient i : p.getIngredient()) {
                uniis.add(i.getUnii());
            }
        }
        return new ArrayList<>(uniis);
    }

    public static String padWithZeros(String src, int size) {
        if (src.length() >= size) {
            return src;
        } else {
            return padWithZeros("0" + src, size);
        }
    }

    public static String getNdc(SplEntry entry) {
        ArrayList<String> ndcs = getNdcs(entry);
        if (ndcs.isEmpty())
            return null;
        return ndcs.get(0);
    }

    public static ArrayList<String> getNdcs(SplEntry entry) {
        ArrayList<String> ndcs = new ArrayList<>();
        if (entry.getProducts() != null && entry.getProducts().getProduct() != null
                && !entry.getProducts().getProduct().isEmpty()) {
            for (Product p : entry.getProducts().getProduct()) {
                if (p.getPkg() != null && !p.getPkg().isEmpty()) {
                    for (Pkg pkg : p.getPkg()) {
                        if (pkg.getNdc() != null)
                            ndcs.add(pkg.getNdc());
                    }
                }
            }
        }
        return ndcs;
    }

    public static List<String> getIdsForPrescriptions(ArrayList<PrescriptionType> rxList) {
        ArrayList<String> retList = new ArrayList<>();
        for (PrescriptionType rx : rxList) {
            retList.add(rx.get_id());
        }
        return retList;
    }
}