net.bioclipse.structuredb.business.StructuredbManager.java Source code

Java tutorial

Introduction

Here is the source code for net.bioclipse.structuredb.business.StructuredbManager.java

Source

/* *****************************************************************************
 * Copyright (c) 2007 - 2009  Jonathan Alvarsson <jonalv@users.sourceforge.net>
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * <http://www.eclipse.org/legal/epl-v10.html>
 *
 * Contact: http://www.bioclipse.net/
 ******************************************************************************/
package net.bioclipse.structuredb.business;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import net.bioclipse.cdk.business.CDKManager;
import net.bioclipse.cdk.business.ICDKManager;
import net.bioclipse.cdk.domain.CDKMolecule;
import net.bioclipse.cdk.domain.ICDKMolecule;
import net.bioclipse.core.ResourcePathTransformer;
import net.bioclipse.core.business.BioclipseException;
import net.bioclipse.core.domain.IMolecule;
import net.bioclipse.core.domain.RecordableList;
import net.bioclipse.core.util.TimeCalculator;
import net.bioclipse.hsqldb.HsqldbUtil;
import net.bioclipse.jobs.BioclipseUIJob;
import net.bioclipse.jobs.IReturner;
import net.bioclipse.managers.business.IBioclipseManager;
import net.bioclipse.structuredb.Activator;
import net.bioclipse.structuredb.FileStoreKeeper;
import net.bioclipse.structuredb.Structuredb;
import net.bioclipse.structuredb.business.IStructureDBChangeListener.DatabaseUpdateType;
import net.bioclipse.structuredb.business.IStructuredbManager.ImportStatistics;
import net.bioclipse.structuredb.domain.Annotation;
import net.bioclipse.structuredb.domain.DBMolecule;
import net.bioclipse.structuredb.domain.Property;
import net.bioclipse.structuredb.domain.RealNumberAnnotation;
import net.bioclipse.structuredb.domain.RealNumberProperty;
import net.bioclipse.structuredb.domain.TextAnnotation;
import net.bioclipse.structuredb.domain.TextProperty;
import net.bioclipse.structuredb.internalbusiness.IStructuredbInstanceManager;
import net.bioclipse.structuredb.persistency.tables.TableCreator;

import org.apache.commons.dbcp.BasicDataSource;
import org.apache.log4j.Logger;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.openscience.cdk.CDKConstants;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

/**
 * @author jonalv
 */
public class StructuredbManager implements IBioclipseManager {

    private Logger logger = Logger.getLogger(StructuredbManager.class);

    private static final Pattern databaseNamePattern = Pattern.compile("(.*?)\\.sdb.*");

    protected ICDKManager cdk = net.bioclipse.cdk.business.Activator.getDefault().getJavaScriptCDKManager();

    //Package protected for testing purposes
    Map<String, IStructuredbInstanceManager> internalManagers = Collections
            .synchronizedMap(new HashMap<String, IStructuredbInstanceManager>());

    //Package protected for testing purposes
    Map<String, ApplicationContext> applicationContexts = Collections
            .synchronizedMap(new HashMap<String, ApplicationContext>());

    private Collection<IStructureDBChangeListener> listeners = Collections
            .synchronizedSet(new HashSet<IStructureDBChangeListener>());

    public StructuredbManager() {
        File[] files = HsqldbUtil.getInstance().getDatabaseFilesDirectory().listFiles();
        if (files != null) {
            for (File file : files) {
                if (file.getName().contains(".sdb")) {
                    loadDatabase(file);
                }
            }
        }
    }

    private void loadDatabase(File file) {

        Matcher m = databaseNamePattern.matcher(file.getName());
        if (m.matches()) {
            String name = m.group(1);
            if (internalManagers.containsKey(name)) {
                return;
            }
            ApplicationContext context = createApplicationcontext(name, true);

            applicationContexts.put(name, context);
            internalManagers.put(name, (IStructuredbInstanceManager) context.getBean("structuredbInstanceManager"));
        }
    }

    public void createDatabase(String databaseName) throws IllegalArgumentException {

        if (internalManagers.containsKey(databaseName)) {
            throw new IllegalArgumentException("Database name " + "already used: " + databaseName);
        }
        TableCreator.INSTANCE.createTables(HsqldbUtil.getInstance().getConnectionUrl(databaseName + ".sdb"));

        Map<String, IStructuredbInstanceManager> newInstances = new HashMap<String, IStructuredbInstanceManager>();

        Map<String, ApplicationContext> newApplicationContexts = new HashMap<String, ApplicationContext>();

        for (String nameKey : internalManagers.keySet()) {
            //TODO: The day we not only handle local databases the row 
            //      here below will need to change
            newApplicationContexts.put(nameKey, createApplicationcontext(nameKey, true));
            newInstances.put(nameKey, (IStructuredbInstanceManager) newApplicationContexts.get(nameKey)
                    .getBean("structuredbInstanceManager"));
        }

        internalManagers = newInstances;
        applicationContexts = newApplicationContexts;

        applicationContexts.put(databaseName, createApplicationcontext(databaseName, true));
        internalManagers.put(databaseName, (IStructuredbInstanceManager) applicationContexts.get(databaseName)
                .getBean("structuredbInstanceManager"));
        logger.info("A new local instance of Structuredb named " + databaseName + " has been created");
        fireDatabasesChanged();
        updateDatabaseDecorators();
    }

    private ApplicationContext createApplicationcontext(String databaseName, boolean local) {

        FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext(
                Structuredb.class.getClassLoader().getResource("applicationContext.xml").toString());

        BasicDataSource dataSource = (BasicDataSource) context.getBean("dataSource");

        if (local) {
            dataSource.setUrl(HsqldbUtil.getInstance().getConnectionUrl(databaseName + ".sdb"));
        } else {
            throw new UnsupportedOperationException("non-local databases not " + "supported in this version");
        }
        return context;
    }

    /**
     * Throw an exception if no such database.
     * 
     * @param databaseName
     * 
     * @throws IllegalArgumentException if no such database
     */
    private void checkDatabaseName(String databaseName) {
        if (!internalManagers.containsKey(databaseName)) {
            throw new IllegalArgumentException("There is no database " + "named: '" + databaseName + "'.\n"
                    + "Use `" + getManagerName() + ".allDatabaseNames` to show all available names.");
        }
    }

    public DBMolecule createMolecule(String databaseName, String moleculeName, ICDKMolecule cdkMolecule)
            throws BioclipseException {

        checkDatabaseName(databaseName);
        DBMolecule m = new DBMolecule(moleculeName, cdkMolecule);
        internalManagers.get(databaseName).insertMolecule(m);
        logger.debug("DBMolecule " + moleculeName + " inserted in " + databaseName);
        updateDatabaseDecorators();
        return m;
    }

    public void deleteDatabase(String databaseName, IProgressMonitor monitor) {
        checkDatabaseName(databaseName);
        internalManagers.get(databaseName).dropDataBase(monitor);
        internalManagers.remove(databaseName);
        applicationContexts.remove(databaseName);
        HsqldbUtil.getInstance().remove(databaseName + ".sdb");
        fireDatabasesChanged();
        updateDatabaseDecorators();
        monitor.done();
    }

    public List<Annotation> allAnnotations(String databaseName) {
        checkDatabaseName(databaseName);
        return internalManagers.get(databaseName).retrieveAllAnnotations();
    }

    public List<DBMolecule> allMolecules(String databaseName) {
        checkDatabaseName(databaseName);
        return internalManagers.get(databaseName).retrieveAllMolecules();
    }

    public List<DBMolecule> allMoleculesByName(String databaseName, String structureName) {
        checkDatabaseName(databaseName);
        return internalManagers.get(databaseName).retrieveStructureByName(structureName);
    }

    public String getManagerName() {
        return "structuredb";
    }

    public ImportStatistics addMoleculesFromSDF(String databaseName, IFile file, IProgressMonitor monitor)
            throws BioclipseException {

        IStructuredbInstanceManager manager = internalManagers.get(databaseName);

        Map<Integer, Exception> failures = new HashMap<Integer, Exception>();

        checkDatabaseName(databaseName);
        if (monitor == null) {
            monitor = new NullProgressMonitor();
        }

        int ticks = 1000000;

        monitor.beginTask("Importing molecules from SDF to database", ticks);
        monitor.subTask("Calculating size of file to import");
        // first, count the number of items to read. 
        // It's a bit of overhead, but adds to the user experience
        int firstTaskTicks = (int) (0.05 * ticks);
        int entries = cdk.numberOfEntriesInSDF(file, new SubProgressMonitor(monitor, firstTaskTicks));
        int maintTaskTick = (ticks - firstTaskTicks) / (entries != 0 ? entries : 1);

        monitor.worked(firstTaskTicks);
        monitor.subTask("Importing structure: " + firstTaskTicks + "/" + entries);
        // now really read the structures

        Iterator<ICDKMolecule> iterator;
        int moleculesRead = 0;
        int importedMolecules = 0;
        monitor.subTask("reading " + moleculesRead + "/" + entries);
        try {
            iterator = cdk.createMoleculeIterator(file);
        } catch (Exception e) {
            throw new IllegalArgumentException("Could not open file:" + file);
        }
        TextAnnotation label = createTextAnnotation(databaseName, "label", file.getName()
                //extracts a name for our 
                //new annotation
                .replaceAll("\\..*?$", ""));

        long start = System.currentTimeMillis();
        int current = 0;
        while (iterator.hasNext() && !monitor.isCanceled()) {
            try {
                String timeEstimation = "";

                if (entries < 500 || current++ % 50 == 0) {
                    if (System.currentTimeMillis() - start > 5000) {
                        timeEstimation = " (" + TimeCalculator.generateTimeRemainEst(start, moleculesRead, entries)
                                + " for file: " + file.getName() + ")";
                    }
                    monitor.subTask("Read: " + moleculesRead + "/" + entries + timeEstimation);
                }

                ICDKMolecule molecule = iterator.next();
                moleculesRead++;

                Object title = molecule.getAtomContainer().getProperty(CDKConstants.TITLE);

                DBMolecule s = new DBMolecule(title == null ? "" : title.toString(), molecule);

                if ("".equals(s.getName())) {
                    s.setName("\"" + s.toSMILES() + "\"");
                }

                manager.insertMoleculeInAnnotation(s, label.getId());
                //            s.addAnnotation( label );

                Map<?, ?> properties = molecule.getAtomContainer().getProperties();

                for (Object o : properties.keySet()) {
                    String key = o.toString();

                    Property p = manager.retrievePropertyByName(key);
                    if (p == null) {
                        p = new TextProperty(key);
                        manager.insertTextProperty((TextProperty) p);
                    }
                    if (!(p instanceof TextProperty)) {
                        p = new TextProperty("Stringified:" + key);
                        manager.insertTextProperty((TextProperty) p);
                    }
                    Annotation a = new TextAnnotation(properties.get(key).toString(), (TextProperty) p);
                    manager.insertTextAnnotation((TextAnnotation) a);
                    manager.annotate(s, a);

                }

                monitor.worked(maintTaskTick);
                importedMolecules++;
            } catch (Exception e) {
                failures.put(moleculesRead, e);
            }
        }
        long end = System.currentTimeMillis();
        logger.debug("addMoleculesFromSDF took " + (end - start) + " ms");
        iterator = null;
        monitor.done();
        fireAnnotationsChanged();
        updateDatabaseDecorators();

        return new ImportStatistics(System.currentTimeMillis() - start, failures, importedMolecules);
    }

    public List<String> allDatabaseNames() {
        return new ArrayList<String>(internalManagers.keySet());
    }

    public List<DBMolecule> allStructureFingerprintSearch(String databaseName, ICDKMolecule molecule)
            throws BioclipseException {

        checkDatabaseName(databaseName);
        List<DBMolecule> dBMolecules = new RecordableList<DBMolecule>();
        Iterator<DBMolecule> iterator = internalManagers.get(databaseName).allStructuresIterator();
        while (iterator.hasNext()) {
            DBMolecule current = iterator.next();
            if (cdk.fingerPrintMatches(new CDKMolecule(current.getAtomContainer()), molecule)) {
                dBMolecules.add(current);
            }
        }
        return dBMolecules;
    }

    public Iterator<DBMolecule> subStructureSearchIterator(String databaseName, IMolecule queryMolecule,
            IProgressMonitor monitor) throws BioclipseException {

        checkDatabaseName(databaseName);
        if (!(queryMolecule instanceof ICDKMolecule)) {
            queryMolecule = cdk.fromSMILES(queryMolecule.toSMILES());
        }
        DBMolecule queryStructure = new DBMolecule("", (ICDKMolecule) queryMolecule);
        int ticks = internalManagers.get(databaseName).numberOfFingerprintMatches(queryStructure);
        if (monitor != null) {
            monitor.beginTask("Sub Structure Search", ticks);
        }

        return new SubStructureIterator(
                internalManagers.get(databaseName).fingerprintSubstructureSearchIterator(queryStructure), cdk,
                (ICDKMolecule) queryMolecule, monitor, ticks);
    }

    public Iterator<DBMolecule> subStructureSearchIterator(String databaseName, IMolecule molecule)
            throws BioclipseException {

        checkDatabaseName(databaseName);
        return subStructureSearchIterator(databaseName, molecule, null);
    }

    public List<DBMolecule> subStructureSearch(String databaseName, IMolecule molecule, IProgressMonitor monitor)
            throws BioclipseException {

        if (monitor == null) {
            monitor = new NullProgressMonitor();
        }

        checkDatabaseName(databaseName);
        List<DBMolecule> dBMolecules = new RecordableList<DBMolecule>();
        Iterator<DBMolecule> iterator = subStructureSearchIterator(databaseName, molecule, monitor);
        while (iterator.hasNext()) {
            dBMolecules.add(iterator.next());
            if (monitor.isCanceled()) {
                throw new OperationCanceledException();
            }
        }
        return dBMolecules;
    }

    public void deleteAnnotation(String databaseName, Annotation annotation, IProgressMonitor monitor) {

        monitor.beginTask("Deleting Annotation", IProgressMonitor.UNKNOWN);
        checkDatabaseName(databaseName);
        internalManagers.get(databaseName).delete(annotation);
        fireAnnotationsChanged();
        updateDatabaseDecorators();
        monitor.done();
    }

    public void deleteStructure(String databaseName, DBMolecule dBMolecule) {

        checkDatabaseName(databaseName);
        internalManagers.get(databaseName).delete(dBMolecule);
        updateDatabaseDecorators();
    }

    public void save(String databaseName, DBMolecule dBMolecule) {

        checkDatabaseName(databaseName);
        internalManagers.get(databaseName).update(dBMolecule);
        updateDatabaseDecorators();
    }

    public void save(String databaseName, Annotation annotation) {

        checkDatabaseName(databaseName);

        //TODO: Figure out why this call is needed
        //Without it it seems this annotation suddenly annotates 0 mols, why?
        annotation.getDBMolecules().size();

        if (annotation instanceof TextAnnotation) {
            internalManagers.get(databaseName).update((TextAnnotation) annotation);
        } else if (annotation instanceof RealNumberAnnotation) {
            internalManagers.get(databaseName).update((RealNumberAnnotation) annotation);
        }
        fireAnnotationsChanged();
        updateDatabaseDecorators();
    }

    public Iterator<DBMolecule> smartsQueryIterator(String databaseName, String smarts) {
        checkDatabaseName(databaseName);
        return smartsQueryIterator(databaseName, smarts, null);
    }

    public static class SMARTSQueryResultList extends RecordableList<DBMolecule> {

        private List<DBMolecule> failedMolecules = new ArrayList<DBMolecule>();

        public void addFailedMolecules(List<DBMolecule> failedMolecules) {
            this.failedMolecules.addAll(failedMolecules);
        }

        public boolean hasFailedMolecules() {
            return failedMolecules.size() > 0;
        }

        public List<DBMolecule> getFailedMolecules() {
            return failedMolecules;
        }
    }

    public List<DBMolecule> smartsQuery(String databaseName, String smarts, IProgressMonitor monitor) {

        checkDatabaseName(databaseName);
        long start = System.currentTimeMillis();
        SMARTSQueryResultList hits = new SMARTSQueryResultList();
        SmartsQueryIterator iterator = smartsQueryIterator(databaseName, smarts, monitor);
        while (iterator.hasNext()) {
            hits.add(iterator.next());
        }

        hits.addFailedMolecules(iterator.getFailedMolecules());

        logger.debug("Time to perform smartQuery: " + (System.currentTimeMillis() - start) + " ms.");
        return hits;
    }

    public SmartsQueryIterator smartsQueryIterator(String databaseName, String smarts, IProgressMonitor monitor) {

        checkDatabaseName(databaseName);
        int numOfMolecules = internalManagers.get(databaseName).numberOfMolecules();
        if (monitor != null) {
            monitor.beginTask("SMARTS querying", numOfMolecules);
        }

        return new SmartsQueryIterator(internalManagers.get(databaseName).allStructuresIterator(), cdk, smarts,
                this, numOfMolecules, monitor);
    }

    public void addListener(IStructureDBChangeListener listener) {
        listeners.add(listener);
    }

    public void removeListener(IStructureDBChangeListener listener) {
        listeners.remove(listener);
    }

    public void fireDatabasesChanged() {
        //the original listeners collection gets edited during the loop
        for (IStructureDBChangeListener l : new ArrayList<IStructureDBChangeListener>(listeners)) {
            l.onDataBaseUpdate(DatabaseUpdateType.DATABASES_CHANGED);
        }
    }

    public void fireAnnotationsChanged() {
        //the original listeners collection gets edited during the loop
        for (IStructureDBChangeListener l : new ArrayList<IStructureDBChangeListener>(listeners)) {
            l.onDataBaseUpdate(DatabaseUpdateType.LABELS_CHANGED);
        }
    }

    public void deleteWithMolecules(String databaseName, Annotation annotation) {
        checkDatabaseName(databaseName);
        deleteWithMolecules(databaseName, annotation, null);
        updateDatabaseDecorators();
    }

    public void deleteWithMolecules(String databaseName, Annotation annotation, IProgressMonitor monitor) {
        checkDatabaseName(databaseName);
        internalManagers.get(databaseName).deleteWithMolecules(annotation, monitor);
        fireAnnotationsChanged();
        updateDatabaseDecorators();
    }

    public RealNumberAnnotation createRealNumberAnnotation(String databaseName, String propertyName, double value)
            throws IllegalArgumentException {

        checkDatabaseName(databaseName);
        RealNumberProperty property = (RealNumberProperty) internalManagers.get(databaseName)
                .retrievePropertyByName(propertyName);
        if (property == null) {
            property = new RealNumberProperty(propertyName);
            internalManagers.get(databaseName).insertRealNumberProperty(property);
        }
        RealNumberAnnotation annotation = new RealNumberAnnotation(value, property);
        internalManagers.get(databaseName).insertRealNumberAnnotation(annotation);
        fireAnnotationsChanged();
        updateDatabaseDecorators();
        return annotation;
    }

    public TextAnnotation createTextAnnotation(String databaseName, String propertyName, String value)
            throws IllegalArgumentException {

        checkDatabaseName(databaseName);
        TextProperty property = (TextProperty) internalManagers.get(databaseName)
                .retrievePropertyByName(propertyName);
        if (property == null) {
            property = new TextProperty(propertyName);
            internalManagers.get(databaseName).insertTextProperty(property);
        }
        TextAnnotation annotation = new TextAnnotation(value, property);
        internalManagers.get(databaseName).insertTextAnnotation(annotation);
        fireAnnotationsChanged();
        updateDatabaseDecorators();
        return annotation;
    }

    public List<TextAnnotation> allLabels(String databaseName) {

        IStructuredbInstanceManager m = internalManagers.get(databaseName);
        if (m == null) {
            return Collections.EMPTY_LIST;
        }
        return m.allLabels();
    }

    public DBMolecule moleculeAtIndexInLabel(String databaseName, int index, TextAnnotation annotation) {

        return internalManagers.get(databaseName).moleculeAtIndexInLabel(index, annotation);

    }

    public int numberOfMoleculesInLabel(String databaseName, TextAnnotation annotation) {

        IStructuredbInstanceManager m = internalManagers.get(databaseName);
        if (m != null) {
            return m.numberOfMoleculesInLabel(annotation);
        }
        return 0;
    }

    public int numberOfMoleculesInDatabaseInstance(String databaseName) {

        IStructuredbInstanceManager m = internalManagers.get(databaseName);
        if (m == null) {
            return 0;
        }
        return m.numberOfMolecules();
    }

    private void updateDatabaseDecorators() {
        if (Activator.getDefault() != null) {
            Activator.getDefault().triggerDatabaseDecoratorsUpdate();
        }
    }

    public void addMoleculesFromFiles(String dbName, List<?> files, IReturner<ImportStatistics> returner,
            IProgressMonitor monitor) {
        long start = System.currentTimeMillis();
        int ticks = 1000000;
        monitor.beginTask("Importing from files", ticks);
        List<IFile> fileList = new ArrayList<IFile>();
        int importedMolecules = 0;
        Map<Integer, Exception> failures = new HashMap<Integer, Exception>();

        for (Object o : files) {
            if (o instanceof IFile) {
                fileList.add((IFile) o);
            } else if (o instanceof String) {
                fileList.add(ResourcePathTransformer.getInstance().transform((String) o));
            } else {
                throw new IllegalArgumentException(o.toString() + " is not a String nor an IFile ");
            }
        }

        ICDKManager cdk = net.bioclipse.cdk.business.Activator.getDefault().getJavaCDKManager();

        for (IFile f : fileList) {
            try {
                String id = f.getContentDescription().getContentType().getId();
                if (id.equals("net.bioclipse.contenttypes.sdf") || id.equals("net.bioclipse.contenttypes.sdf2d")
                        || id.equals("net.bioclipse.contenttypes.sdf3d")
                        || id.equals("net.bioclipse.contenttypes.sdf0d")) {

                    class Returner implements IReturner<ImportStatistics> {
                        ImportStatistics statistics;

                        public void completeReturn(ImportStatistics object) {
                            statistics = object;
                        }

                        public void partialReturn(ImportStatistics object) {
                        }
                    }

                    ImportStatistics s = addMoleculesFromSDF(dbName, f,
                            new SubProgressMonitor(monitor, ticks / fileList.size()));
                    failures.putAll(s.failures);
                    importedMolecules += s.importedMolecules;

                } else {
                    for (ICDKMolecule m : cdk.loadMolecules(f)) {
                        createMolecule(dbName, "", m);
                    }
                }
            } catch (Exception e) {
                throw new RuntimeException("Ooops", e);
            }
        }

        returner.completeReturn(
                new ImportStatistics(System.currentTimeMillis() - start, failures, importedMolecules));
    }

    public Iterator<DBMolecule> allStructuresIterator(String databaseName) {
        checkDatabaseName(databaseName);
        return internalManagers.get(databaseName).allStructuresIterator();
    }

    public void updateMolecule(String dbName, DBMolecule molecule) {
        checkDatabaseName(dbName);
        internalManagers.get(dbName).update(molecule);
        fireAnnotationsChanged();
        updateDatabaseDecorators();
    }

    public void deleteAllDatabases(IProgressMonitor m) {
        m.beginTask("Deleting databases (can not be aborted)", IProgressMonitor.UNKNOWN);
        for (String databaseName : new HashSet<String>(internalManagers.keySet())) {
            internalManagers.remove(databaseName);
            applicationContexts.remove(databaseName);
            HsqldbUtil.getInstance().remove(databaseName + ".sdb");
        }

        FileStoreKeeper.FILE_STORE.deleteAll();

        fireDatabasesChanged();
        updateDatabaseDecorators();
        m.done();
    }

    public void annotate(String databaseName, DBMolecule m, Annotation a) {
        checkDatabaseName(databaseName);
        internalManagers.get(databaseName).annotate(m, a);
    }

    public Collection<String> getAvailableProperties(String databaseName, TextAnnotation annotation) {
        checkDatabaseName(databaseName);
        return internalManagers.get(databaseName).getAvailableProperties(annotation);
    }

    public Annotation getAnnotationById(String databaseName, String id) {
        checkDatabaseName(databaseName);
        return internalManagers.get(databaseName).getAnnotationById(id);
    }
}