org.getobjects.eoaccess.EODatabase.java Source code

Java tutorial

Introduction

Here is the source code for org.getobjects.eoaccess.EODatabase.java

Source

/*
  Copyright (C) 2006-2007 Helge Hess
    
  This file is part of Go.
    
  Go is free software; you can redistribute it and/or modify it under
  the terms of the GNU Lesser General Public License as published by the
  Free Software Foundation; either version 2, or (at your option) any
  later version.
    
  Go 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 Lesser General Public
  License for more details.
    
  You should have received a copy of the GNU Lesser General Public
  License along with Go; see the file COPYING.  If not, write to the
  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
  02111-1307, USA.
*/

package org.getobjects.eoaccess;

import java.net.URL;
import java.util.Collection;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.getobjects.eocontrol.EOEditingContext;
import org.getobjects.eocontrol.EOFetchSpecification;
import org.getobjects.foundation.NSClassLookupContext;
import org.getobjects.foundation.NSDisposable;
import org.getobjects.foundation.NSJavaRuntime;
import org.getobjects.foundation.NSObject;

/**
 * EODatabase
 * <p>
 * The database wraps an EOAdaptor and acts as a central entry point for
 * EO based access to the database. That is, to database rows represented
 * as objects (subclasses of EOActiveRecord).
 * <p>
 * You usually aquire an EOActiveDataSource from the central EODatabase
 * object and then use that to perform queries against a specific table.
 * <p>
 * Example:
 * <pre>
 *   EODatabase         db = new EODatabase(adaptor, null);
 *   EOActiveDataSource ds = db.dataSourceForEntity("person");
 *   EOActiveRecord donald =
 *     ds.findByMatchingAll("lastname", "Duck", "firstname", "Donald");</pre>
 *
 * <p>
 * @see EOActiveDataSource
 * @see EOAdaptor
 */
public class EODatabase extends NSObject implements NSDisposable, NSClassLookupContext {
    protected static final Log log = LogFactory.getLog("EODatabase");

    protected NSClassLookupContext classLookup;
    protected EOAdaptor adaptor;

    public EODatabase(EOAdaptor _adaptor, NSClassLookupContext _clslookup) {
        this.adaptor = _adaptor;
        this.classLookup = _clslookup != null ? _clslookup : this;
    }

    public EODatabase(String _url, EOModel _model, NSClassLookupContext _clslup) {
        this(EOAdaptor.adaptorWithURL(_url, _model), _clslup);
    }

    /* accessors */

    /**
     * Returns the adaptor used by the EODatabase to fetch/update objects in the
     * database.
     *
     * @return the EOAdaptor object assigned to this database.
     */
    public EOAdaptor adaptor() {
        return this.adaptor;
    }

    /**
     * Retrieves the EOModel set in the adaptor.
     *
     * @return the EOModel object assigned to the underlying EOAdaptor.
     */
    public EOModel model() {
        return this.adaptor != null ? this.adaptor.model() : null;
    }

    /**
     * The class lookup context is responsible for resolving simple class names
     * to fully qualified ones. Eg if you specified 'Account' as the class name
     * in an EOModel, this context is responsible to resolving this to a FQN,
     * eg org.opengroupware.lib.Account.
     *
     * In Go web applications you can usually pass in the WOResourceManager, eg
     *   new EODatabase(adaptor, myApplication.resourceManager());
     *
     * @return the class lookup context used by the database to resolve names
     */
    public NSClassLookupContext classLookupContext() {
        return this.classLookup;
    }

    public Class classForEntity(EOEntity _entity) {
        String clsName = _entity != null ? _entity.className() : null;
        Class cls = null;

        if (this.classLookupContext() != null) {
            if (clsName == null || clsName.length() == 0)
                cls = EOActiveRecord.class;
            else {
                cls = this.classLookupContext().lookupClass(clsName);
                if (cls == null)
                    log.error("failed to lookup EO class: " + clsName);
            }
        } else {
            if (clsName == null || clsName.length() == 0)
                cls = EOActiveRecord.class;
            else {
                cls = NSJavaRuntime.NSClassFromString(clsName);
                if (cls == null)
                    log.error("failed to lookup EO class: " + clsName);
            }
        }

        if (cls == null) {
            cls = EOActiveRecord.class;
            log.error("got not class for EO, using EOActiveRecord: " + cls);
        }

        return cls;
    }

    /**
     * Uses the EOModel of the EOAdaptor to find the specified EOEntity object.
     *
     * @param _entityName name of the entity to lookup
     * @return an EOEntity object or null if the name could not be found
     */
    public EOEntity entityNamed(final String _entityName) {
        if (this.adaptor == null)
            return null;

        final EOModel model = this.model();
        return model != null ? model.entityNamed(_entityName) : null;
    }

    /**
     * Returns the EOEntity object associated with a given object. If the object
     * is an instance of EOActiveRecord, the record itself will be asked. If it
     * isn't (eg a POJO), the model will be asked whether one of the entities is
     * mapped to the given object.
     *
     * @param _object the object which we want to get an EOEntity for
     * @return the EOEntity assigned to the object or null if none is assigned
     */
    public EOEntity entityForObject(final Object _object) {
        if (_object == null)
            return null;

        /* try to ask the object itself */

        EOEntity entity = null;
        if (_object instanceof EOActiveRecord) {
            entity = ((EOActiveRecord) _object).entity();
            if (entity != null)
                return entity;
        }

        /* check whether its a generic object */

        final Class clazz = _object.getClass();
        if (clazz == EOActiveRecord.class) {
            /* can't determine entities for generic objects */
            return null;
        }

        /* search in model */

        EOModel model = this.model();
        return model != null ? model.entityForObject(_object) : null;
    }

    /* operations */

    /**
     * Construct a new EOActiveDataSource for the given entity.
     *
     * @param _ename name of the entity which we want to have a datasource for
     * @return a new EOActiveDataSource object
     */
    public EOAccessDataSource dataSourceForEntity(final String _ename) {
        return this.dataSourceForEntity(null, _ename);
    }

    /**
     * Construct a new EOActiveDataSource for the given entity.
     *
     * @param _entity the entity which we want to have a datasource for
     * @return a new EOActiveDataSource object
     */
    public EOAccessDataSource dataSourceForEntity(final EOEntity _entity) {
        return this.dataSourceForEntity(_entity, null);
    }

    /**
     * Determine the class of the datasource which should be used for the given
     * entity. Usually we have no special mapping but just use
     * EOActiveDataSource, but you can choose to map an own datasource class
     * in the model.
     *
     * @param _entity entity for which we want to determine the datasource class
     * @return an EOActiveDataSource subclass which shall be used for the entity
     */
    public Class dataSourceClassForEntity(final EOEntity _entity) {
        if (_entity == null) {
            log.debug("got no entity to detect the database datasource!");
            return EOActiveDataSource.class;
        }

        final String dsClassName = _entity.dataSourceClassName();
        if (dsClassName == null || dsClassName.length() == 0) {
            if (log.isDebugEnabled())
                log.debug("entity has no custom datasource class assigned: " + _entity);
            return EOActiveDataSource.class;
        }

        Class dsClass = this.classLookupContext().lookupClass(dsClassName);
        if (dsClass == null) {
            log.error("did not find datasource class '" + dsClassName + "' of " + "entity: " + _entity);
        }
        return dsClass;
    }

    public EOAccessDataSource dataSourceForEntity(final EOEntity _entity, String _ename) {
        EOEntity entity = _entity;
        if (entity == null && _ename != null)
            entity = this.entityNamed(_ename);
        if (_ename == null && entity != null)
            _ename = entity.name();

        Class dsClass = this.dataSourceClassForEntity(entity);
        if (dsClass == null)
            return null;

        EOAccessDataSource ds = null;

        if (EOActiveDataSource.class.isAssignableFrom(dsClass)) {
            ds = (EOAccessDataSource) NSJavaRuntime.NSAllocateObject(dsClass,
                    new Class[] { EODatabase.class, String.class }, new Object[] { this, _ename });
        } else if (EODatabaseDataSource.class.isAssignableFrom(dsClass)) {
            /* its preferable not to use this facility */
            EOEditingContext ec = new EOEditingContext(new EODatabaseContext(this));
            ds = (EOAccessDataSource) NSJavaRuntime.NSAllocateObject(dsClass,
                    new Class[] { EOEditingContext.class, String.class }, new Object[] { ec, _ename });
        } else if (EOAdaptorDataSource.class.isAssignableFrom(dsClass)) {
            ds = (EOAccessDataSource) NSJavaRuntime.NSAllocateObject(dsClass,
                    new Class[] { EOAdaptor.class, EOEntity.class }, new Object[] { this.adaptor(), _entity });
        } else {
            log.warn("unexpected datasource class: " + dsClass);
            ds = (EOAccessDataSource) NSJavaRuntime.NSAllocateObject(dsClass);
        }

        if (ds == null)
            log.error("could not allocate datasource: " + dsClass);

        return ds;
    }

    public List objectsWithFetchSpecification(final EOFetchSpecification _fs) {
        if (_fs == null)
            return null;

        final EOAccessDataSource ds = this.dataSourceForEntity(_fs.entityName());
        List results = null;
        try {
            ds.setFetchSpecification(_fs);
            results = ds.fetchObjects();
            if (ds.lastException() != null)
                log.error("exception during fetch on datasource: " + ds);
        } finally {
            if (ds != null)
                ds.dispose();
        }
        return results;
    }

    public Exception performDatabaseOperation(final EODatabaseOperation _op) {
        if (_op == null)
            return null; /* nothing to do */

        final EODatabaseChannel channel = new EODatabaseChannel(this);
        Exception error = null;
        try {
            error = channel.performDatabaseOperations(new EODatabaseOperation[] { _op });
        } finally {
            if (channel != null)
                channel.dispose();
        }
        return error;
    }

    /**
     * Performs the set of database operations in a single database transaction.
     * 
     * @param _ops - a collection of EODatabaseOperation objects
     * @return an exception if the operations failed, null if all is OK
     */
    public Exception performDatabaseOperations(final Collection<EODatabaseOperation> _ops) {
        if (_ops == null || _ops.size() == 0)
            return null; /* nothing to do */

        final EODatabaseChannel channel = new EODatabaseChannel(this);
        Exception error = null;
        try {
            error = channel.begin();

            if (error == null) {
                error = channel.performDatabaseOperations(_ops.toArray(new EODatabaseOperation[0]));

                if (error == null)
                    error = channel.commit();

                if (error != null) // this also happens if the commit fails
                    channel.rollback(); // yes, ignore follow-up errors
            }
        } finally {
            if (channel != null)
                channel.dispose();
        }
        return error;
    }

    /* class lookup context (can be used if the subclass lives in the EO pkg) */

    /**
     * The EODatabase lookupClass method first checks relative to the package
     * of the EODatabase subclass.
     * For example if your OGoDatabase subclass lives in org.ogo.db, a class
     * OGoPerson will be looked up as 'org.ogo.db.OGoPerson'.
     * If this lookup fails, a global lookup is performed.
     * <p>
     * Note: you can pass in other NSClassLookupContext objects!
     */
    public Class lookupClass(final String _name) {
        if (_name == null)
            return null;

        // TODO: cache

        String fullname = this.getClass().getPackage().getName() + "." + _name;
        Class cls = NSJavaRuntime.NSClassFromString(fullname);
        if (cls != null)
            return cls;

        cls = NSJavaRuntime.NSClassFromString(_name);
        if (cls != null)
            return cls;

        log.warn("did not find requested class: " + _name);
        return null;
    }

    /* low level adaptor creation method */

    /**
     * This method first derives the model name from the given class. If the
     * class ends with 'Database', this is replace with 'Model.xml'. For example:
     * 'OGoDatabase' gives 'OGoModel.xml'.
     * The model is the loaded.
     */
    public static EOAdaptor dbAdaptorForURL(Class _cls, String _dbURL) {
        if (_cls == null) {
            log.error("got no EODatabase class to create the adaptor for:" + _dbURL);
            return null;
        }
        if (_dbURL == null) {
            log.error("got no URL create the adaptor for:" + _cls);
            return null;
        }

        /* derive model name (eg OGoDatabase => OGoModel.xml) */

        String modelName = _cls.getSimpleName();
        if (modelName.endsWith("Database"))
            modelName = modelName.substring(0, modelName.length() - 8);
        modelName += "Model.xml";

        /* load model */

        URL modelURL = _cls.getResource(modelName);
        EOModel modelPattern = null;
        try {
            if (modelURL != null)
                modelPattern = EOModel.loadModel(modelURL);
            else
                log.warn("did not find database model resource: " + modelName);
        } catch (Exception e) {
            log.error("could not load database model " + modelName, e);
            return null;
        }

        /* setup adaptor */

        EOAdaptor adaptor = EOAdaptor.adaptorWithURL(_dbURL, modelPattern);
        if (adaptor == null) {
            log.error("got no adaptor for DB URL: " + _dbURL);
            return null;
        }

        if (!adaptor.testConnect()) {
            log.error("adaptor could not connect to DB URL: " + _dbURL);
            return null;
        }

        /* make adaptor fetch the model from the database (if necessary) */

        EOModel model = adaptor.model();
        if (model == null) {
            log.error("adaptor could not retrieve model for DB URL: " + _dbURL);
            return null;
        }

        return adaptor;
    }

    /* dispose */

    public void dispose() {
        if (this.adaptor != null)
            this.adaptor.dispose();
        this.adaptor = null;
    }

    /* description */

    public void appendAttributesToDescription(final StringBuilder _d) {
        super.appendAttributesToDescription(_d);

        if (this.adaptor != null)
            _d.append(" adaptor=" + this.adaptor);
    }
}