Java tutorial
/* * 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.Constants; import org.phenotips.data.IndexedPatientData; import org.phenotips.data.Patient; import org.phenotips.data.PatientData; import org.phenotips.data.PatientDataController; import org.phenotips.vocabulary.Vocabulary; import org.phenotips.vocabulary.VocabularyManager; import org.phenotips.vocabulary.VocabularyTerm; import org.xwiki.component.annotation.Component; import org.xwiki.model.EntityType; import org.xwiki.model.reference.EntityReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Provider; import javax.inject.Singleton; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import org.json.JSONArray; import org.json.JSONObject; import org.slf4j.Logger; import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.doc.XWikiDocument; import com.xpn.xwiki.objects.BaseObject; import com.xpn.xwiki.objects.BaseStringProperty; import com.xpn.xwiki.objects.StringListProperty; /** * Handles the patients gene variants. * * @version $Id: a0564baeb44d709091e741f74f06644bf44add8e $ * @since 1.3M1 */ @Component(roles = { PatientDataController.class }) @Named("variant") @Singleton public class VariantListController extends AbstractComplexController<Map<String, String>> { /** The XClass used for storing variant data. */ private static final EntityReference VARIANT_CLASS_REFERENCE = new EntityReference("GeneVariantClass", EntityType.DOCUMENT, Constants.CODE_SPACE_REFERENCE); private static final String VARIANTS_STRING = "variants"; private static final String CONTROLLER_NAME = VARIANTS_STRING; private static final String VARIANTS_ENABLING_FIELD_NAME = "genes"; private static final String INTERNAL_VARIANT_KEY = "cdna"; private static final String INTERNAL_GENE_KEY = "gene"; private static final String INTERNAL_PROTEIN_KEY = "protein"; private static final String INTERNAL_TRANSCRIPT_KEY = "transcript"; private static final String INTERNAL_DBSNP_KEY = "dbsnp"; private static final String INTERNAL_ZYGOSITY_KEY = "zygosity"; private static final String INTERNAL_EFFECT_KEY = "effect"; private static final String INTERNAL_INTERPRETATION_KEY = "interpretation"; private static final String INTERNAL_INHERITANCE_KEY = "inheritance"; private static final String INTERNAL_EVIDENCE_KEY = "evidence"; private static final String INTERNAL_SEGREGATION_KEY = "segregation"; private static final String INTERNAL_SANGER_KEY = "sanger"; private static final String INTERNAL_CHROMOSOME_KEY = "chromosome"; private static final String INTERNAL_START_POSITION_KEY = "start_position"; private static final String INTERNAL_END_POSITION_KEY = "end_position"; private static final String INTERNAL_REFERENCE_GENOME_KEY = "reference_genome"; private static final String JSON_VARIANT_KEY = INTERNAL_VARIANT_KEY; private static final String JSON_GENE_KEY = INTERNAL_GENE_KEY; // older 1.3-xx gene key in variant json private static final String JSON_OLD_GENE_KEY = "genesymbol"; private static final String JSON_PROTEIN_KEY = INTERNAL_PROTEIN_KEY; private static final String JSON_TRANSCRIPT_KEY = INTERNAL_TRANSCRIPT_KEY; private static final String JSON_DBSNP_KEY = INTERNAL_DBSNP_KEY; private static final String JSON_ZYGOSITY_KEY = INTERNAL_ZYGOSITY_KEY; private static final String JSON_EFFECT_KEY = INTERNAL_EFFECT_KEY; private static final String JSON_INTERPRETATION_KEY = INTERNAL_INTERPRETATION_KEY; private static final String JSON_INHERITANCE_KEY = INTERNAL_INHERITANCE_KEY; private static final String JSON_EVIDENCE_KEY = INTERNAL_EVIDENCE_KEY; private static final String JSON_SEGREGATION_KEY = INTERNAL_SEGREGATION_KEY; private static final String JSON_SANGER_KEY = INTERNAL_SANGER_KEY; private static final String JSON_CHROMOSOME_KEY = INTERNAL_CHROMOSOME_KEY; private static final String JSON_START_POSITION_KEY = INTERNAL_START_POSITION_KEY; private static final String JSON_END_POSITION_KEY = INTERNAL_END_POSITION_KEY; private static final String JSON_REFERENCE_GENOME_KEY = INTERNAL_REFERENCE_GENOME_KEY; private static final List<String> ZYGOSITY_VALUES = Arrays.asList("heterozygous", "homozygous", "hemizygous"); private static final List<String> EFFECT_VALUES = Arrays.asList("missense", "nonsense", "insertion_in_frame", "insertion_frameshift", "deletion_in_frame", "deletion_frameshift", "indel_in_frame", "indel_frameshift", "duplication", "repeat_expansion", "synonymous", "other"); private static final List<String> INTERPRETATION_VALUES = Arrays.asList("pathogenic", "likely_pathogenic", "variant_u_s", "likely_benign", "benign", "investigation_n"); private static final List<String> INHERITANCE_VALUES = Arrays.asList("denovo_germline", "denovo_s_mosaicism", "maternal", "paternal", "unknown"); private static final List<String> EVIDENCE_VALUES = Arrays.asList("rare", "predicted", "reported"); private static final List<String> SEGREGATION_VALUES = Arrays.asList("segregates", "not_segregates"); private static final List<String> SANGER_VALUES = Arrays.asList("positive", "negative"); private static final List<String> CHROMOSOME_VALUES = Arrays.asList("1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "X", "Y"); private static final List<String> REFERENCE_GENOME_VALUES = Arrays.asList("GRCh37", "GRCh38", "NCBI36"); /** The vocabulary manager that actually does all the work. */ @Inject private VocabularyManager vocabularyManager; @Inject private Logger logger; private Vocabulary hgnc; /** 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 Arrays.asList(INTERNAL_VARIANT_KEY, INTERNAL_GENE_KEY, INTERNAL_PROTEIN_KEY, INTERNAL_TRANSCRIPT_KEY, INTERNAL_DBSNP_KEY, INTERNAL_ZYGOSITY_KEY, INTERNAL_EFFECT_KEY, INTERNAL_INTERPRETATION_KEY, INTERNAL_INHERITANCE_KEY, INTERNAL_EVIDENCE_KEY, INTERNAL_SEGREGATION_KEY, INTERNAL_SANGER_KEY, INTERNAL_CHROMOSOME_KEY, INTERNAL_START_POSITION_KEY, INTERNAL_END_POSITION_KEY, INTERNAL_REFERENCE_GENOME_KEY); } @Override protected List<String> getBooleanFields() { return Collections.emptyList(); } @Override protected List<String> getCodeFields() { return Collections.emptyList(); } @Override public PatientData<Map<String, String>> load(Patient patient) { try { XWikiDocument doc = patient.getXDocument(); List<BaseObject> variantXWikiObjects = doc.getXObjects(VARIANT_CLASS_REFERENCE); if (variantXWikiObjects == null || variantXWikiObjects.isEmpty()) { return null; } List<Map<String, String>> allVariants = new LinkedList<>(); for (BaseObject variantObject : variantXWikiObjects) { if (variantObject == null || variantObject.getFieldList().isEmpty()) { continue; } Map<String, String> singleVariant = new LinkedHashMap<>(); for (String property : getProperties()) { String value = getFieldValue(variantObject, property); if (value == null) { continue; } singleVariant.put(property, value); } allVariants.add(singleVariant); } if (allVariants.isEmpty()) { return null; } else { return new IndexedPatientData<>(getName(), allVariants); } } catch (Exception e) { this.logger.error(ERROR_MESSAGE_LOAD_FAILED, e.getMessage()); } return null; } private String getFieldValue(BaseObject variantObject, String property) { if (INTERNAL_EVIDENCE_KEY.equals(property)) { StringListProperty fields = (StringListProperty) variantObject.getField(property); if (fields == null || fields.getList().size() == 0) { return null; } return fields.getTextValue(); } else if (INTERNAL_START_POSITION_KEY.equals(property) || INTERNAL_END_POSITION_KEY.equals(property)) { int value = variantObject.getIntValue(property, -1); if (value == -1) { return null; } return Integer.toString(value); } else { BaseStringProperty field = (BaseStringProperty) variantObject.getField(property); if (field == null) { return null; } return field.getValue(); } } @Override public void writeJSON(Patient patient, JSONObject json, Collection<String> selectedFieldNames) { if (selectedFieldNames != null && !selectedFieldNames.contains(VARIANTS_ENABLING_FIELD_NAME)) { return; } PatientData<Map<String, String>> data = patient.getData(getName()); if (data == null || data.size() == 0) { if (selectedFieldNames != null && selectedFieldNames.contains(VARIANTS_ENABLING_FIELD_NAME)) { json.put(getJsonPropertyName(), new JSONArray()); } return; } Iterator<Map<String, String>> iterator = data.iterator(); // put() is placed here because we want to create the property iff at least one field is set/enabled // (by this point we know there is some data since iterator.hasNext() == true) json.put(getJsonPropertyName(), new JSONArray()); JSONArray container = json.getJSONArray(getJsonPropertyName()); Map<String, String> internalToJSONkeys = new HashMap<>(); internalToJSONkeys.put(JSON_VARIANT_KEY, INTERNAL_VARIANT_KEY); internalToJSONkeys.put(JSON_GENE_KEY, INTERNAL_GENE_KEY); internalToJSONkeys.put(JSON_PROTEIN_KEY, INTERNAL_PROTEIN_KEY); internalToJSONkeys.put(JSON_TRANSCRIPT_KEY, INTERNAL_TRANSCRIPT_KEY); internalToJSONkeys.put(JSON_DBSNP_KEY, INTERNAL_DBSNP_KEY); internalToJSONkeys.put(JSON_ZYGOSITY_KEY, INTERNAL_ZYGOSITY_KEY); internalToJSONkeys.put(JSON_EFFECT_KEY, INTERNAL_EFFECT_KEY); internalToJSONkeys.put(JSON_INTERPRETATION_KEY, INTERNAL_INTERPRETATION_KEY); internalToJSONkeys.put(JSON_INHERITANCE_KEY, INTERNAL_INHERITANCE_KEY); internalToJSONkeys.put(JSON_EVIDENCE_KEY, INTERNAL_EVIDENCE_KEY); internalToJSONkeys.put(JSON_SEGREGATION_KEY, INTERNAL_SEGREGATION_KEY); internalToJSONkeys.put(JSON_SANGER_KEY, INTERNAL_SANGER_KEY); internalToJSONkeys.put(JSON_CHROMOSOME_KEY, INTERNAL_CHROMOSOME_KEY); internalToJSONkeys.put(JSON_START_POSITION_KEY, INTERNAL_START_POSITION_KEY); internalToJSONkeys.put(JSON_END_POSITION_KEY, INTERNAL_END_POSITION_KEY); internalToJSONkeys.put(JSON_REFERENCE_GENOME_KEY, INTERNAL_REFERENCE_GENOME_KEY); while (iterator.hasNext()) { Map<String, String> item = iterator.next(); if (!StringUtils.isBlank(item.get(INTERNAL_VARIANT_KEY))) { JSONObject nextVariant = new JSONObject(); for (String key : internalToJSONkeys.keySet()) { if (!StringUtils.isBlank(item.get(key))) { if (INTERNAL_EVIDENCE_KEY.equals(key)) { nextVariant.put(key, new JSONArray(item.get(internalToJSONkeys.get(key)).split("\\|"))); } else { nextVariant.put(key, item.get(internalToJSONkeys.get(key))); } } } container.put(nextVariant); } } } @Override public PatientData<Map<String, String>> readJSON(JSONObject json) { if (json == null || !json.has(getJsonPropertyName())) { return null; } List<String> enumValueKeys = Arrays.asList(INTERNAL_ZYGOSITY_KEY, INTERNAL_EFFECT_KEY, INTERNAL_INTERPRETATION_KEY, INTERNAL_INHERITANCE_KEY, INTERNAL_SEGREGATION_KEY, INTERNAL_SANGER_KEY); Map<String, List<String>> enumValues = new LinkedHashMap<>(); enumValues.put(INTERNAL_ZYGOSITY_KEY, ZYGOSITY_VALUES); enumValues.put(INTERNAL_EFFECT_KEY, EFFECT_VALUES); enumValues.put(INTERNAL_INTERPRETATION_KEY, INTERPRETATION_VALUES); enumValues.put(INTERNAL_INHERITANCE_KEY, INHERITANCE_VALUES); enumValues.put(INTERNAL_EVIDENCE_KEY, EVIDENCE_VALUES); enumValues.put(INTERNAL_SEGREGATION_KEY, SEGREGATION_VALUES); enumValues.put(INTERNAL_SANGER_KEY, SANGER_VALUES); enumValues.put(INTERNAL_CHROMOSOME_KEY, CHROMOSOME_VALUES); enumValues.put(INTERNAL_REFERENCE_GENOME_KEY, REFERENCE_GENOME_VALUES); try { JSONArray variantsJson = json.getJSONArray(this.getJsonPropertyName()); List<Map<String, String>> allVariants = new LinkedList<>(); List<String> variantSymbols = new ArrayList<>(); for (int i = 0; i < variantsJson.length(); ++i) { JSONObject variantJson = variantsJson.getJSONObject(i); // discard it if variant cDNA is not present in the variantJson, or is whitespace, empty or duplicate if (StringUtils.isBlank(variantJson.optString(INTERNAL_VARIANT_KEY)) || variantSymbols.contains(variantJson.getString(INTERNAL_VARIANT_KEY)) // storing variant without gene name is pointless as it can not be displayed || StringUtils.isBlank(variantJson.optString(JSON_GENE_KEY)) && StringUtils.isBlank(variantJson.optString(JSON_OLD_GENE_KEY))) { continue; } Map<String, String> singleVariant = parseVariantJson(variantJson, enumValues, enumValueKeys); if (singleVariant.isEmpty()) { continue; } allVariants.add(singleVariant); variantSymbols.add(variantJson.getString(INTERNAL_VARIANT_KEY)); } if (allVariants.isEmpty()) { return null; } else { return new IndexedPatientData<>(getName(), allVariants); } } catch (Exception e) { this.logger.error("Could not load variants from JSON", e.getMessage()); } return null; } /** * Supports both 1.3-m5 and older 1.3-xx format. 1.3-m5 and newer variant JSON format: {"gene": ENSEMBL_Id [, ...] } * 1.3-old format: {"genesymbol": HGNC_Symbol [, ...] } */ private Map<String, String> parseVariantJson(JSONObject variantJson, Map<String, List<String>> enumValues, List<String> enumValueKeys) { Map<String, String> singleVariant = new LinkedHashMap<>(); // v1.2.x json compatibility // gene ID is either the "gene" field, or, if missing, the "genesymbol" field String geneId = variantJson.optString(JSON_GENE_KEY); if (StringUtils.isBlank(geneId)) { geneId = variantJson.optString(JSON_OLD_GENE_KEY); geneId = getEnsemblId(geneId); } singleVariant.put(INTERNAL_GENE_KEY, geneId); for (String property : this.getJSONProperties()) { if (variantJson.has(property)) { parseVariantProperty(property, variantJson, enumValues, singleVariant, enumValueKeys); } } return singleVariant; } private void parseVariantProperty(String property, JSONObject variantJson, Map<String, List<String>> enumValues, Map<String, String> singleVariant, List<String> enumValueKeys) { String field = ""; if (INTERNAL_EVIDENCE_KEY.equals(property) && variantJson.getJSONArray(property).length() > 0) { JSONArray fieldArray = variantJson.getJSONArray(property); for (Object value : fieldArray) { if (enumValues.get(property).contains(value)) { field += "|" + value; } } singleVariant.put(property, field); } else if ((INTERNAL_START_POSITION_KEY.equals(property) || INTERNAL_END_POSITION_KEY.equals(property)) && !StringUtils.isBlank(variantJson.getString(property))) { String value = variantJson.optString(property); if (NumberUtils.isDigits(value)) { singleVariant.put(property, value); } } else if (enumValueKeys.contains(property) && !StringUtils.isBlank(variantJson.getString(property))) { field = variantJson.getString(property); if (enumValues.get(property).contains(field.toLowerCase())) { singleVariant.put(property, field); } } else if (!StringUtils.isBlank(variantJson.getString(property))) { field = variantJson.getString(property); singleVariant.put(property, field); } } private List<String> getJSONProperties() { return Arrays.asList(JSON_VARIANT_KEY, JSON_PROTEIN_KEY, JSON_TRANSCRIPT_KEY, JSON_DBSNP_KEY, JSON_ZYGOSITY_KEY, JSON_EFFECT_KEY, JSON_INTERPRETATION_KEY, JSON_INHERITANCE_KEY, JSON_EVIDENCE_KEY, JSON_SEGREGATION_KEY, JSON_SANGER_KEY, JSON_CHROMOSOME_KEY, JSON_START_POSITION_KEY, JSON_END_POSITION_KEY, JSON_REFERENCE_GENOME_KEY); } /** * Gets EnsemblID corresponding to the HGNC symbol. * * @param gene the string representation a gene, either geneSymbol (e.g. NOD2) or some other kind of ID * @return if gene is a valid geneSymbol, the corresponding Ensembl ID. Otherwise the original gene value */ private String getEnsemblId(String gene) { final VocabularyTerm term = this.getTerm(gene); @SuppressWarnings("unchecked") final List<String> ensemblIdList = term != null ? (List<String>) term.get("ensembl_gene_id") : null; final String ensemblId = ensemblIdList != null && !ensemblIdList.isEmpty() ? ensemblIdList.get(0) : null; // retain information as is if we can't find Ensembl ID. return StringUtils.isBlank(ensemblId) ? gene : ensemblId; } private VocabularyTerm getTerm(String gene) { // lazy-initialize HGNC if (this.hgnc == null) { this.hgnc = getHGNCVocabulary(); if (this.hgnc == null) { return null; } } return this.hgnc.getTerm(gene); } private Vocabulary getHGNCVocabulary() { try { return vocabularyManager.getVocabulary("HGNC"); } catch (Exception ex) { // this should not happen except when mocking, but does not hurt to catch in any case this.logger.error("Error loading component [{}]", ex.getMessage(), ex); return null; } } @Override public void save(Patient patient) { PatientData<Map<String, String>> variants = patient.getData(this.getName()); if (variants == null || !variants.isIndexed()) { return; } XWikiContext context = this.xcontextProvider.get(); patient.getXDocument().removeXObjects(VARIANT_CLASS_REFERENCE); Iterator<Map<String, String>> iterator = variants.iterator(); while (iterator.hasNext()) { try { Map<String, String> variant = iterator.next(); BaseObject xwikiObject = patient.getXDocument().newXObject(VARIANT_CLASS_REFERENCE, context); for (String property : this.getProperties()) { String value = variant.get(property); if (value != null) { if (INTERNAL_START_POSITION_KEY.equals(property) || INTERNAL_END_POSITION_KEY.equals(property)) { xwikiObject.setIntValue(property, Integer.valueOf(value)); } else { xwikiObject.set(property, value, context); } } } } catch (Exception e) { this.logger.error("Failed to save a specific variant: [{}]", e.getMessage()); } } } }