Changeset 685


Ignore:
Timestamp:
May 3, 2012, 10:46:56 PM (5 years ago)
Author:
hailiang.mei@…
Message:

Fixed >100 checkstyle errors.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/code/conceptwiki/imports/imports-swissprot/src/main/java/nl/nbic/conceptwiki/imports/swissprot/UniprotImport.java

    r664 r685  
    6262
    6363/**
    64  * http://www.javarants.com/2006/04/30/simple-and-efficient-xml-parsing-using-jaxb-2-0/
     64 * This is the SwissProt import script for Neo4J based conceptwiki.
     65 * Several input documents are required:
     66 *   #1, ${swissprot.releasefile}
     67 *   SwissProt release file in XML format
     68 *   http://www.uniprot.org/downloads
     69 *   
     70 *   #2, ${swissprot.specificProteinMapping}
     71 *   Species specific protein mapping list between UMLS and SP. e.g.
     72 *   "C1571376|Q91ZQ5". Created by Kees Burger.
     73 *   
     74 *   #3, ${swissprot.duplicatedCUIsInSpecificProteinMapping}
     75 *   Since we might have species specific proteins in UMLS that are mapped
     76 *   to a generic protein in SP in the document #2. We need to fix them
     77 *   during the bootstrap. A list of all duplicated CUIs is created for this
     78 *   purpose.
     79 *   
     80 *   #4, ${swissprot.genericProteinMapping}
     81 *   Species non-specific protein mapping list between UMLS and SP. e.g.
     82 *   "Q2RIB9|C0668703". Created by Kees Burger.
     83 *   
     84 *   #5, ${swissprot.genericGeneMapping}
     85 *   Species non-specific gene mapping list between UMLS and SP. e.g.
     86 *   "P07642|C0017338". Created by Kees Burger.
     87 *   
     88 *   #6, ${swissprot.drugbankTargetMapping}
     89 *   Some SP proteins are drug targets in Drugbank. This list contains those SP
     90 *   proteins and their mapped drug target ID in Drugbank. e.g. "1|P45059".
     91 *   Created by Kees Burger.
     92 *   
     93 *   #7, ${swissprot.drugbankDrugURLMapping}
     94 *   Some SP proteins are drugs in Drugbank. This list contains those SP
     95 *   proteins and their mapped drug entries in Drugbank.
     96 *   e.g. "P16860|http://drugbank.ca/drugs/DB04899".
     97 *   Created by Kees Burger.
     98 *   
    6599 * @author <a href="mailto:kees.burger@nbic.nl">Kees Burger</a>
    66  * @author Leon
     100 * @author <a href="mailto:hailiang.mei@nbic.nl">Leon Mei</a>
    67101 */
    68102@Service
     
    72106     */
    73107    private static final Logger logger = LoggerFactory.getLogger(UniprotImport.class);
    74     /**
    75      * Concept service layer interaction.
     108   
     109    /**
     110     * Neo4J transaction size during import.
     111     */
     112    private static final int CHUNK_SIZE = 5000;
     113   
     114    /**
     115     * ID of NLM branch.
     116     * #1: community
     117     * #2: NLM
     118     */
     119    private static final int NLM_BRANCH_ID = 2;
     120   
     121    /**
     122     * ID of NLM branch.
     123     * #3: SwissProt
     124     */
     125    private static final int SWISSPROT_BRANCH_ID = 3;
     126   
     127    /**
     128     * ID of NLM branch.
     129     * #4: ChemSpider
     130     * #5: ConceptWiki
     131     */
     132    private static final int CONCEPTWIKI_BRANCH_ID = 5;
     133   
     134    /**
     135     * English Language object.
     136     */     
     137    private static final Language LANG_EN = new LanguageImpl("en");
     138
     139    /**
     140     * The URL prefix of SwissProt entry page.
     141     */
     142    private static final String UNIPROT_URL_PREFIX = "http://www.uniprot.org/uniprot/";
     143   
     144    /**
     145     * The URL prefix of chembl entry page.
     146     */
     147    private static final String CHEM2BIO2RDF_CHEMBL_PREFIX = "http://chem2bio2rdf.org/chembl/resource/chembl_targets/";
     148   
     149    /**
     150     * The URL prefix of PDB entry page.
     151     */
     152    private static final String PDB_URL_PREFIX = "http://www.pdb.org/pdb/explore/explore.do?pdbId=";
     153
     154    /**
     155     * The URL prefix of drugbank target entry page.
     156     */
     157    private static final String DRUGBANK_TARGET_URL_PREFIX = "http://www4.wiwiss.fu-berlin.de/drugbank/resource/targets/";
     158
     159    /**
     160     * The label of non species specific tag.
     161     */
     162    private static final String NON_SPECIES_SPECIFIC_TAG_LABEL = "Non species specific";
     163   
     164    /**
     165     * Concept service reference.
    76166     */
    77167    @Inject
    78168    private ConceptService conceptService;
     169    /**
     170     * Tag storage reference. Needed for creating new tags.
     171     */
    79172    @Inject
    80173    private TagStorageService tagStorageService;
     174    /**
     175     * Neo4J graphDB reference. Needed for transcations.
     176     */
    81177    @Inject
    82178    private GraphTemplate graphTemplate;
     179    /**
     180     * JDBC access object to chembl mapping DB.
     181     */
    83182    @Inject
    84183    private JdbcTemplate jdbcChemblTemplate;
     
    90189    private String uniprotReleaseFile;
    91190
     191    /**
     192     * Species specific protein mapping list between UMLS and SP.
     193     */
    92194    @Value("${swissprot.specificProteinMapping}")
    93195    private String specificProteinMapping;
     
    101203    private String duplicatedCUIsInSpecificProteinMapping;
    102204
     205    /**
     206     * Species non-specific protein mapping list between UMLS and SP.
     207     */
    103208    @Value("${swissprot.genericProteinMapping}")
    104209    private String genericProteinMapping;
    105210
     211    /**
     212     * Species non-specific gene mapping list between UMLS and SP.
     213     */
    106214    @Value("${swissprot.genericGeneMapping}")
    107215    private String genericGeneMapping;
    108216
     217    /**
     218     * Mapping list between SP and drugbank target.
     219     */     
    109220    @Value("${swissprot.drugbankTargetMapping}")
    110221    private String drugbankTargetMappingFile;
    111222
     223    /**
     224     * Mapping list between SP and drugbank drugs.
     225     */     
    112226    @Value("${swissprot.drugbankDrugURLMapping}")
    113227    private String drugbankDrugURLMapping;
    114228
    115     private Language LANG_EN = new LanguageImpl("en");
    116 
    117     /**
    118      * id of NLM branch.
    119      * #1: community
    120      * #2: NLM
    121      * #3: SwissProt
    122      * #4: ChemSpider
    123      * #5: ConceptWiki
    124      */
    125     private int nlmBranchId = 2;
    126     private int swissprotBranchId = 3;
    127     private int conceptWikiBranchId = 5;
    128 
    129     private static final String UNIPROT_URL_PREFIX = "http://www.uniprot.org/uniprot/";
    130     //we don't support drugbank yet.
    131     //private static final String DRUGBANK_URL_PREFIX = "http://drugbank.ca/drugs/";
    132     private static final String CHEM2BIO2RDF_CHEMBL_PREFIX = "http://chem2bio2rdf.org/chembl/resource/chembl_targets/";
    133     private static final String PDB_URL_PREFIX = "http://www.pdb.org/pdb/explore/explore.do?pdbId=";
    134 
    135     // e.g. http://www4.wiwiss.fu-berlin.de/drugbank/resource/targets/54
    136     private static final String DRUGBANK_TARGET_URL_PREFIX = "http://www4.wiwiss.fu-berlin.de/drugbank/resource/targets/";
    137 
    138     private static final String NON_SPECIES_SPECIFIC_TAG_LABEL = "Non species specific";
    139 
     229    /**
     230     * A tag for non species specific concepts.
     231     */
    140232    private Concept nonSpeciesSpecificTag;
    141233
     234    /**
     235     * The protein concept created during UMLS import.
     236     */
    142237    private Concept proteinSemanticType;
     238   
     239    /**
     240     * The gene&genome concept created during UMLS import.
     241     */
    143242    private Concept geneSemanticType;
    144243
    145     private static Map<String, String> genericProteinMapper = Maps.newHashMap();
    146     private static Map<String, String> genericGeneMapper = Maps.newHashMap();
    147     private static Map<String, String> specificProteinMapper = Maps.newHashMap();
    148     private static Set<String> duplicatedCUIs = Sets.newHashSet();
    149 
     244    /**
     245     * A convenience map that stores mapped generic proteins: <SwissProt ID, UMLS ID>.
     246     */
     247    private Map<String, String> genericProteinMapper = Maps.newHashMap();
     248   
     249    /**
     250     * A convenience map that stores mapped generic genes: <SwissProt ID, UMLS ID>.
     251     */
     252    private Map<String, String> genericGeneMapper = Maps.newHashMap();
     253   
     254    /**
     255     * A convenience map that stores mapped specific proteins: <SwissProt ID, UMLS ID>.
     256     */
     257    private Map<String, String> specificProteinMapper = Maps.newHashMap();
     258       
     259    /**
     260     * A convenience map that stores already created protein concepts: <protein name in lower case, concept>.
     261     */
    150262    private Map<String, Concept> genericProteinConcepts = Maps.newHashMap();
     263   
     264    /**
     265     * A convenience map that stores already created protein concepts: <gene name in lower case, concept>.
     266     */
    151267    private Map<String, Concept> genericGeneConcepts = Maps.newHashMap();
     268   
     269    /**
     270     * A convenience map that stores mappings to chembl: <SwissProt ID, Chembl ID>.
     271     */
    152272    private Map<String, String> uniprot2ChemblMapping = Maps.newHashMap();
    153273
     274    /**
     275     * A convenience map that stores mappings to drugbank targets: <SwissProt ID, target ID>.
     276     */
    154277    private Map<String, String> drugbankTargetMapping = Maps.newHashMap();
     278   
     279    /**
     280     * A convenience map that stores mappings to drugbank drugs: <SwissProt ID, drug's URLs>.
     281     */
    155282    private Map<String, List<Url>> drugbankDrugMapping = Maps.newHashMap();
    156283
    157     public boolean bootstrap() throws IOException {
    158 
    159         final String proteinSemTypeUuid = conceptService.getConceptsByLabel("Amino Acid, Peptide, or Protein", new QueryScopeImpl(), 1).get(0).getUuid();
    160         proteinSemanticType = conceptService.getConcept(proteinSemTypeUuid, nlmBranchId);
    161 
    162         if (proteinSemanticType == null) {
    163             logger.error("cannot find concept Amino Acid, Peptide, or Protein");
    164             return false;
    165         }
    166         logger.info("Amino Acid, Peptide, or Protein: " + proteinSemanticType.getUuid());
    167 
    168         final String geneSemTypeUuid = conceptService.getConceptsByLabel("Gene or Genome", new QueryScopeImpl(), 1).get(0).getUuid();
    169         geneSemanticType = conceptService.getConcept(geneSemTypeUuid, nlmBranchId);
    170 
    171         if (geneSemanticType == null) {
    172             logger.error("cannot find concept Gene or Genome");
    173             return false;
    174         }
    175         logger.info("Gene or Genome: " + geneSemanticType.getUuid());
     284    /**
     285     * Bootstrap step including loading all input files into convenience maps. 
     286     * @return if the bootstrap is successful or not
     287     * @throws IOException in case I/O errors
     288     */
     289    public boolean bootstrap() throws IOException {       
    176290
    177291        String line;
     
    180294
    181295        BufferedReader mappingReader = new BufferedReader(new FileReader(duplicatedCUIsInSpecificProteinMapping));
     296        final String fieldSeparator = "|";
     297        final Set<String> duplicatedCUIs = Sets.newHashSet();
    182298        while ((line = mappingReader.readLine()) != null) {
    183             final StringTokenizer lineTokenizer = new StringTokenizer(line, "|");
     299            final StringTokenizer lineTokenizer = new StringTokenizer(line, fieldSeparator);
    184300            //C1571376|Q91ZQ5
    185301            cId = lineTokenizer.nextToken();
     
    190306        mappingReader = new BufferedReader(new FileReader(specificProteinMapping));
    191307        while ((line = mappingReader.readLine()) != null) {
    192             final StringTokenizer lineTokenizer = new StringTokenizer(line, "|");
     308            final StringTokenizer lineTokenizer = new StringTokenizer(line, fieldSeparator);
    193309            //C1571376|Q91ZQ5
    194310            cId = lineTokenizer.nextToken();
     
    209325                continue;
    210326            }
    211             final StringTokenizer lineTokenizer = new StringTokenizer(line, "|");
     327            final StringTokenizer lineTokenizer = new StringTokenizer(line, fieldSeparator);
    212328            //O14561|C0001369
    213329            spId = lineTokenizer.nextToken();
     
    219335        mappingReader = new BufferedReader(new FileReader(genericGeneMapping));
    220336        while ((line = mappingReader.readLine()) != null) {
    221             final StringTokenizer lineTokenizer = new StringTokenizer(line, "|");
     337            final StringTokenizer lineTokenizer = new StringTokenizer(line, fieldSeparator);
    222338            //Q5XGC8|C1421558
    223339            spId = lineTokenizer.nextToken();
     
    236352        while ((line = mappingReader.readLine()) != null) {
    237353            //10|P06737 and SP id could be empty
    238             final StringTokenizer lineTokenizer = new StringTokenizer(line, "|");
     354            final StringTokenizer lineTokenizer = new StringTokenizer(line, fieldSeparator);
    239355            final String target = lineTokenizer.nextToken();
    240356            if (lineTokenizer.hasMoreTokens()) {
     
    247363        mappingReader = new BufferedReader(new FileReader(drugbankDrugURLMapping));
    248364        while ((line = mappingReader.readLine()) != null) {
    249             final StringTokenizer lineTokenizer = new StringTokenizer(line, "|");
     365            final StringTokenizer lineTokenizer = new StringTokenizer(line, fieldSeparator);
    250366            //P09919|http://drugbank.ca/drugs/DB00019|http://drugbank.ca/drugs/DB00099
    251367            spId = lineTokenizer.nextToken();
     
    259375        mappingReader.close();
    260376
     377        final String proteinSemTypeUuid =
     378                conceptService.getConceptsByLabel("Amino Acid, Peptide, or Protein", new QueryScopeImpl(), 1).get(0).getUuid();
     379        proteinSemanticType = conceptService.getConcept(proteinSemTypeUuid, NLM_BRANCH_ID);
     380
     381        if (proteinSemanticType == null) {
     382            logger.error("cannot find concept Amino Acid, Peptide, or Protein");
     383            return false;
     384        }
     385        logger.info("Amino Acid, Peptide, or Protein: " + proteinSemanticType.getUuid());
     386
     387        final String geneSemTypeUuid = conceptService.getConceptsByLabel("Gene or Genome", new QueryScopeImpl(), 1).get(0).getUuid();
     388        geneSemanticType = conceptService.getConcept(geneSemTypeUuid, NLM_BRANCH_ID);
     389
     390        if (geneSemanticType == null) {
     391            logger.error("cannot find concept Gene or Genome");
     392            return false;
     393        }
     394        logger.info("Gene or Genome: " + geneSemanticType.getUuid());
     395       
    261396        // create non species specific tags.
    262397        createTags();
     
    264399        logger.info("bootstapping is done");
    265400
    266 
    267401        return true;
    268402    }
    269403
     404    /**
     405     * Bootstrap tags.
     406     */
    270407    private void createTags() {
    271408        final Transaction tx = graphTemplate.getGraphDb().beginTx();
     
    275412        nonSpeciesSpecificTag = conceptService.createConcept(
    276413                new ConceptImpl.Builder().labels(Sets.newHashSet(nonSpeciesSpecificLabel)).build(),
    277                 conceptWikiBranchId);
     414                CONCEPTWIKI_BRANCH_ID);
    278415
    279416        // make them as tags
     
    284421    }
    285422
     423    /**
     424     * Add a tag in the conceptwiki branch.
     425     * @param concept an existing concept
     426     * @param tag an existing tag
     427     */
    286428    private void tagConceptInConceptWikiBranch(final Concept concept, final Concept tag) {
    287429
     
    292434                    .withTags(newTags));
    293435
    294             conceptService.updateConcept(concept.getUuid(), changeset, conceptWikiBranchId);
     436            conceptService.updateConcept(concept.getUuid(), changeset, CONCEPTWIKI_BRANCH_ID);
    295437        }
    296438    }
    297439
    298 
    299     public void setReleaseFile(final String locationReleaseFile) {
    300         uniprotReleaseFile = locationReleaseFile;
    301     }
    302 
     440    /**
     441     * The import step.
     442     * @throws FileNotFoundException in case XML file is not found
     443     * @throws XMLStreamException in case XML reading error
     444     * @throws JAXBException in case XML parsing error
     445     */
    303446    public void run() throws FileNotFoundException, XMLStreamException, JAXBException {
    304447        final XMLInputFactory xif = XMLInputFactory.newInstance();
     
    306449        xsr.nextTag();
    307450
    308         int counter = 0;
    309 
     451        int counter = 0;       
     452
     453        final String organismPrefix = " (";
     454        final String organismPostfix = ")";
     455       
    310456        Transaction tx = graphTemplate.getGraphDb().beginTx();
    311457
     
    355501            String organism = "";
    356502            for (OrganismNameType ont : entry.getOrganism().getName()) {
    357                 if (ont.getType().equals("scientific")) {
     503                if ("scientific".equals(ont.getType())) {
    358504                    organism = ont.getValue();
    359505                }
     
    363509            final String proteinRecommendedName = entry.getProtein().getRecommendedName().getFullName().getValue();
    364510            // organism should be included in the preferred name
    365             proteinLabels.add(new LabelImpl(LabelType.PREFERRED, proteinRecommendedName + " (" + organism + ")", LANG_EN));
     511            proteinLabels.add(new LabelImpl(LabelType.PREFERRED, proteinRecommendedName + organismPrefix + organism + organismPostfix, LANG_EN));
    366512            for (EvidencedStringType shortRecommendedName : entry.getProtein().getRecommendedName().getShortName()) {
    367513                proteinLabels.add(new LabelImpl(LabelType.ALTERNATIVE, shortRecommendedName.getValue(), LANG_EN));
     
    387533            for (Gene gene : entry.getGene()) {
    388534                for (GeneNameType gnt : gene.getName()) {
    389                     if (gnt.getType().equals("primary")) {
     535                    if ("primary".equals(gnt.getType())) {
    390536                        // organism should be included in the preferred label. And there can be only 1 preferred label
    391537                        if (genePrimaryName == null) {
    392538                            //store the 1st primary term as gene preferred label.
    393539                            genePrimaryName = gnt.getValue();
    394                             geneLabels.add(new LabelImpl(LabelType.PREFERRED, gnt.getValue() + " (" + organism + ")", LANG_EN));
     540                            geneLabels.add(new LabelImpl(LabelType.PREFERRED, gnt.getValue() + organismPrefix + organism + organismPostfix, LANG_EN));
    395541                        } else {
    396542                            geneLabels.add(new LabelImpl(LabelType.ALTERNATIVE, gnt.getValue(), LANG_EN));
     
    416562            if (genericProteinUMLSId != null) {
    417563                // this generic organism protein exists in UMLS, use it as a tag
    418                 final List<Concept> concepts = conceptService.getConceptsByNotation(genericProteinUMLSId, QueryScopeImpl.FULL_SCOPE, 1, nlmBranchId);
     564                final List<Concept> concepts =
     565                        conceptService.getConceptsByNotation(genericProteinUMLSId, QueryScopeImpl.FULL_SCOPE, 1, NLM_BRANCH_ID);
    419566                if (concepts.size() == 1) {
    420567                    final Concept genericConcept = Iterables.getOnlyElement(concepts);
     
    448595
    449596                    final Label genericProteinName = new LabelImpl(LabelType.PREFERRED, proteinRecommendedName, LANG_EN);
    450                     genericConceptlabels.add(genericProteinName); // preferred label for new concept
     597                    genericConceptlabels.add(genericProteinName);
    451598                    final Concept genericConceptEnvelop = new ConceptImpl.Builder()
    452599                        .labels(genericConceptlabels)
     
    454601                        .build();
    455602
    456                     final Concept createdGenericConcept = conceptService.createConcept(genericConceptEnvelop, conceptWikiBranchId);
     603                    final Concept createdGenericConcept = conceptService.createConcept(genericConceptEnvelop, CONCEPTWIKI_BRANCH_ID);
    457604
    458605                    // make it a tag
     
    483630                if (genericGeneUMLSId != null) {
    484631                    // this generic organism gene exists in UMLS, use it as a tag
    485                     final List<Concept> concepts = conceptService.getConceptsByNotation(genericGeneUMLSId, QueryScopeImpl.FULL_SCOPE, 1, nlmBranchId);
     632                    final List<Concept> concepts =
     633                            conceptService.getConceptsByNotation(genericGeneUMLSId, QueryScopeImpl.FULL_SCOPE, 1, NLM_BRANCH_ID);
    486634                    if (concepts.size() == 1) {
    487635                        final Concept genericConcept = Iterables.getOnlyElement(concepts);
     
    514662                        // this generic concept does not exist, create it.
    515663                        final Label genericGeneName = new LabelImpl(LabelType.PREFERRED, genePrimaryName.toUpperCase(), LANG_EN);
    516                         genericConceptlabels.add(genericGeneName); // preferred label for new concept
     664                        genericConceptlabels.add(genericGeneName);
    517665                        final Concept genericConceptEnvelop = new ConceptImpl.Builder()
    518666                            .labels(genericConceptlabels)
     
    520668                            .build();
    521669
    522                         final Concept createdGenericConcept = conceptService.createConcept(genericConceptEnvelop, conceptWikiBranchId);
     670                        final Concept createdGenericConcept = conceptService.createConcept(genericConceptEnvelop, CONCEPTWIKI_BRANCH_ID);
    523671
    524672                        // make it a tag
     
    547695                createNewProteinConcept = true;
    548696            } else {
    549                 final List<Concept> concepts = conceptService.getConceptsByNotation(umlsId, QueryScopeImpl.FULL_SCOPE, 1, nlmBranchId); //getConceptsByNotation(umlsId, QueryScopeImpl.FULL_SCOPE, 10);
     697                final List<Concept> concepts =
     698                        conceptService.getConceptsByNotation(umlsId, QueryScopeImpl.FULL_SCOPE, 1, NLM_BRANCH_ID);
    550699                if (concepts.size() == 1) {
    551700                    // map to existing protein concept
     
    566715                                .withTags(newTags));
    567716                        // The new information about this protein from this SP entry will be added in the SP branch
    568                         conceptService.updateConcept(umlsConceptUuid, changeset, swissprotBranchId);
     717                        conceptService.updateConcept(umlsConceptUuid, changeset, SWISSPROT_BRANCH_ID);
    569718                    }
    570719
     
    587736                        .tags(Sets.newHashSet(proteinSemanticType))
    588737                        .build();
    589                 final Concept createdProteinConcept = conceptService.createConcept(proteinConcept, swissprotBranchId);
     738                final Concept createdProteinConcept = conceptService.createConcept(proteinConcept, SWISSPROT_BRANCH_ID);
    590739
    591740                // a generic protein tag was created in conceptwiki branch and it should tag to this SP protein concept in conceptwiki branch too.
     
    601750                        .tags(Sets.newHashSet(geneSemanticType))
    602751                        .build();
    603                 final Concept createdGeneConcept = conceptService.createConcept(geneConceptEnvelop, swissprotBranchId);
     752                final Concept createdGeneConcept = conceptService.createConcept(geneConceptEnvelop, SWISSPROT_BRANCH_ID);
    604753
    605754                // a generic gene tag was created in conceptwiki branch and it should tag to this SP gene concept in conceptwiki branch too.
     
    608757
    609758            counter++;
    610             if (counter >= 5000) {
     759            if (counter >= CHUNK_SIZE) {
    611760                tx.success();
    612761                tx.finish();
     
    621770    }
    622771
    623     public static void main(final String[] args) throws Exception {
     772    /**
     773     * Main loop.
     774     * @throws Exception in case any internal erros
     775     */
     776    public static void main() throws Exception {
     777        //load the application context.
    624778        final ApplicationContext ctx = new ClassPathXmlApplicationContext("/nl/nbic/conceptwiki/imports/swissprot/import-swissprot-context.xml");
    625779
    626780        final UniprotImport uniprotImport = (UniprotImport)ctx.getBean(UniprotImport.class);
    627781        final long start = System.currentTimeMillis();
     782       
    628783        if (uniprotImport.bootstrap()) {
     784            // if bootstrap succeeds, run the import.
    629785            uniprotImport.run();
    630786            System.out.println("Import took " + (System.currentTimeMillis() - start) + "ms");
Note: See TracChangeset for help on using the changeset viewer.