org.phenotips.data.internal.controller.FeaturesController.java Source code

Java tutorial

Introduction

Here is the source code for org.phenotips.data.internal.controller.FeaturesController.java

Source

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see http://www.gnu.org/licenses/
 */
package org.phenotips.data.internal.controller;

import org.phenotips.data.Feature;
import org.phenotips.data.FeatureMetadatum;
import org.phenotips.data.IndexedPatientData;
import org.phenotips.data.Patient;
import org.phenotips.data.PatientData;
import org.phenotips.data.PatientDataController;
import org.phenotips.data.internal.PhenoTipsFeature;
import org.phenotips.data.internal.PhenoTipsFeatureMetadatum;

import org.xwiki.component.annotation.Component;
import org.xwiki.model.reference.EntityReference;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
import javax.inject.Singleton;

import org.apache.commons.lang3.StringUtils;
import org.json.JSONArray;
import org.json.JSONObject;
import org.slf4j.Logger;

import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.objects.BaseObject;
import com.xpn.xwiki.objects.BaseProperty;
import com.xpn.xwiki.objects.ListProperty;

/**
 * Handles the patients features.
 *
 * @version $Id: 3608c879bce5fca6826f62db156d74ea30703fab $
 * @since 1.3RC1
 */
@Component(roles = { PatientDataController.class })
@Named("features")
@Singleton
public class FeaturesController extends AbstractComplexController<Feature> {
    /** used for generating JSON and reading from JSON. */
    private static final String JSON_KEY_FEATURES = "features";

    private static final String JSON_KEY_NON_STANDARD_FEATURES = "nonstandard_features";

    private static final String CONTROLLER_NAME = JSON_KEY_FEATURES;

    /** Known phenotype properties. */
    private static final String PHENOTYPE_POSITIVE_PROPERTY = "phenotype";

    private static final String PRENATAL_PHENOTYPE_PREFIX = "prenatal_";

    private static final String PRENATAL_PHENOTYPE_PROPERTY = PRENATAL_PHENOTYPE_PREFIX
            + PHENOTYPE_POSITIVE_PROPERTY;

    private static final String PHENOTYPE_NEGATIVE_PROPERTY = PhenoTipsFeature.NEGATIVE_PHENOTYPE_PREFIX
            + PHENOTYPE_POSITIVE_PROPERTY;

    private static final String NEGATIVE_PRENATAL_PHENOTYPE_PROPERTY = PhenoTipsFeature.NEGATIVE_PHENOTYPE_PREFIX
            + PRENATAL_PHENOTYPE_PROPERTY;

    private static final String[] PHENOTYPE_PROPERTIES = new String[] { PHENOTYPE_POSITIVE_PROPERTY,
            PHENOTYPE_NEGATIVE_PROPERTY, PRENATAL_PHENOTYPE_PROPERTY, NEGATIVE_PRENATAL_PHENOTYPE_PROPERTY };

    @Inject
    private Logger logger;

    /** Provides access to the current execution context. */
    @Inject
    private Provider<XWikiContext> xcontextProvider;

    @Override
    public String getName() {
        return CONTROLLER_NAME;
    }

    @Override
    protected String getJsonPropertyName() {
        return CONTROLLER_NAME;
    }

    @Override
    protected List<String> getProperties() {
        return Collections.emptyList();
    }

    @Override
    protected List<String> getBooleanFields() {
        return Collections.emptyList();
    }

    @Override
    protected List<String> getCodeFields() {
        return Collections.emptyList();
    }

    @SuppressWarnings("unchecked")
    @Override
    public IndexedPatientData<Feature> load(Patient patient) {
        try {
            BaseObject data = patient.getXDocument().getXObject(Patient.CLASS_REFERENCE);
            if (data == null) {
                return null;
            }

            List<Feature> features = new ArrayList<>();

            Collection<BaseProperty<EntityReference>> fields = data.getFieldList();
            for (BaseProperty<EntityReference> field : fields) {
                if (field == null || !field.getName().matches("(?!extended_)(.*_)?phenotype")
                        || !ListProperty.class.isInstance(field)) {
                    continue;
                }
                ListProperty values = (ListProperty) field;
                for (String value : values.getList()) {
                    if (StringUtils.isNotBlank(value)) {
                        features.add(new PhenoTipsFeature(patient.getXDocument(), values, value));
                    }
                }
            }
            if (features.isEmpty()) {
                return null;
            } else {
                return new IndexedPatientData<>(getName(), features);
            }
        } catch (Exception e) {
            this.logger.error(ERROR_MESSAGE_LOAD_FAILED, e.getMessage());
        }
        return null;
    }

    @Override
    public void writeJSON(Patient patient, JSONObject json, Collection<String> selectedFieldNames) {
        if (selectedFieldNames != null && !isFieldSuffixIncluded(selectedFieldNames, PHENOTYPE_POSITIVE_PROPERTY)) {
            return;
        }

        PatientData<Feature> data = patient.getData(getName());
        json.put(JSON_KEY_FEATURES, featuresToJSON(data, selectedFieldNames));
        json.put(JSON_KEY_NON_STANDARD_FEATURES, nonStandardFeaturesToJSON(data, selectedFieldNames));
    }

    /** creates & returns a new JSON array of all patient features (as JSON objects). */
    private JSONArray featuresToJSON(PatientData<Feature> data, Collection<String> selectedFields) {
        JSONArray featuresJSON = new JSONArray();
        if (data != null) {
            Iterator<Feature> iterator = data.iterator();
            while (iterator.hasNext()) {
                Feature phenotype = iterator.next();
                if (StringUtils.isBlank(phenotype.getId())
                        || !isFieldIncluded(selectedFields, phenotype.getType())) {
                    continue;
                }
                JSONObject featureJSON = phenotype.toJSON();
                if (featureJSON != null) {
                    featuresJSON.put(featureJSON);
                }
            }
        }
        return featuresJSON;
    }

    private JSONArray nonStandardFeaturesToJSON(PatientData<Feature> data, Collection<String> selectedFields) {
        JSONArray featuresJSON = new JSONArray();
        if (data != null) {
            Iterator<Feature> iterator = data.iterator();
            while (iterator.hasNext()) {
                Feature phenotype = iterator.next();
                if (StringUtils.isNotBlank(phenotype.getId())
                        || !isFieldIncluded(selectedFields, phenotype.getType())) {
                    continue;
                }
                JSONObject featureJSON = phenotype.toJSON();
                if (featureJSON != null) {
                    featuresJSON.put(featureJSON);
                }
            }
        }
        return featuresJSON;
    }

    private boolean isFieldIncluded(Collection<String> selectedFields, String fieldName) {
        return (selectedFields == null || selectedFields.contains(fieldName));
    }

    private boolean isFieldSuffixIncluded(Collection<String> selectedFields, String fieldSuffix) {
        if (selectedFields == null) {
            return true;
        }
        for (String fieldName : selectedFields) {
            if (StringUtils.endsWith(fieldName, fieldSuffix)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public PatientData<Feature> readJSON(JSONObject json) {
        if (json == null || !json.has(JSON_KEY_FEATURES) && !json.has(JSON_KEY_NON_STANDARD_FEATURES)) {
            return null;
        }

        try {
            JSONArray jsonFeatures = joinArrays(json.optJSONArray(JSON_KEY_FEATURES),
                    json.optJSONArray(JSON_KEY_NON_STANDARD_FEATURES));

            // keep this instance of PhenotipsPatient in sync with the document: reset features
            List<Feature> features = new ArrayList<>();

            for (int i = 0; i < jsonFeatures.length(); i++) {
                JSONObject featureInJSON = jsonFeatures.optJSONObject(i);
                if (featureInJSON == null) {
                    continue;
                }

                Feature phenotipsFeature = new PhenoTipsFeature(featureInJSON);
                features.add(phenotipsFeature);
            }
            return new IndexedPatientData<>(getName(), features);
        } catch (Exception e) {
            this.logger.error("Failed to update patient features from JSON: [{}]", e.getMessage(), e);
            return null;
        }
    }

    private JSONArray joinArrays(JSONArray jsonOne, JSONArray jsonTwo) {
        JSONArray result = new JSONArray();
        if (jsonOne == null && jsonTwo != null) {
            result = jsonTwo;
        } else if (jsonOne != null) {
            result = jsonOne;
            if (jsonTwo != null && jsonTwo.length() > 0) {
                for (int i = 0; i < jsonTwo.length(); i++) {
                    result.put(jsonTwo.get(i));
                }
            }
        }
        return result;
    }

    @Override
    public void save(Patient patient) {
        PatientData<Feature> features = patient.getData(this.getName());
        if (features == null || !features.isIndexed()) {
            return;
        }

        XWikiDocument docX = patient.getXDocument();
        BaseObject data = docX.getXObject(Patient.CLASS_REFERENCE);
        XWikiContext context = this.xcontextProvider.get();

        // new feature lists (for setting values in the Wiki document)
        Map<String, List<String>> featuresMap = new TreeMap<>();
        Iterator<Feature> iterator = features.iterator();
        while (iterator.hasNext()) {
            Feature feature = iterator.next();
            String featureType = feature.getType();
            if (!feature.isPresent()) {
                featureType = PhenoTipsFeature.NEGATIVE_PHENOTYPE_PREFIX + featureType;
            }
            if (featuresMap.keySet().contains(featureType)) {
                featuresMap.get(featureType).add(feature.getValue());
            } else {
                List<String> newFeatureType = new LinkedList<>();
                newFeatureType.add(feature.getValue());
                featuresMap.put(featureType, newFeatureType);
            }
        }

        // to reset values in the document null them first
        for (String type : PHENOTYPE_PROPERTIES) {
            data.set(type, null, context);
        }

        for (String type : featuresMap.keySet()) {
            data.set(type, featuresMap.get(type), context);
        }

        try {
            // update features' metadata objects in document
            updateMetaData(features, docX, context);

            // update features' categories objects in document
            updateCategories(features, docX, context);
        } catch (Exception e) {
            this.logger.error("Failed to update phenotypes: [{}]", e.getMessage());
        }
    }

    private void updateMetaData(PatientData<Feature> features, XWikiDocument doc, XWikiContext context)
            throws XWikiException {
        doc.removeXObjects(FeatureMetadatum.CLASS_REFERENCE);
        Iterator<Feature> iterator = features.iterator();
        while (iterator.hasNext()) {
            Feature feature = iterator.next();
            @SuppressWarnings("unchecked")
            Map<String, FeatureMetadatum> metadataMap = (Map<String, FeatureMetadatum>) feature.getMetadata();
            if (metadataMap.isEmpty() && feature.getNotes().isEmpty()) {
                continue;
            }

            BaseObject metaObject = doc.newXObject(FeatureMetadatum.CLASS_REFERENCE, context);
            metaObject.set(PhenoTipsFeature.META_PROPERTY_NAME, feature.getPropertyName(), context);
            metaObject.set(PhenoTipsFeature.META_PROPERTY_VALUE, feature.getValue(), context);
            for (String type : metadataMap.keySet()) {
                PhenoTipsFeatureMetadatum metadatum = (PhenoTipsFeatureMetadatum) metadataMap.get(type);
                metaObject.set(type, metadatum.getId(), context);
            }
            metaObject.set("comments", feature.getNotes(), context);
        }
    }

    private void updateCategories(PatientData<Feature> features, XWikiDocument doc, XWikiContext context)
            throws XWikiException {
        doc.removeXObjects(PhenoTipsFeature.CATEGORY_CLASS_REFERENCE);
        Iterator<Feature> iterator = features.iterator();
        while (iterator.hasNext()) {
            Feature feature = iterator.next();
            List<String> categories = feature.getCategories();
            if (categories.isEmpty()) {
                continue;
            }

            BaseObject categoriesObject = doc.newXObject(PhenoTipsFeature.CATEGORY_CLASS_REFERENCE, context);
            categoriesObject.set(PhenoTipsFeature.META_PROPERTY_NAME, feature.getPropertyName(), context);
            categoriesObject.set(PhenoTipsFeature.META_PROPERTY_VALUE, feature.getValue(), context);
            categoriesObject.set(PhenoTipsFeature.META_PROPERTY_CATEGORIES, categories, context);
        }
    }

}