Dashboard > Apache Cayenne > Browse Space > News Items for
  Apache Cayenne Log In | Sign Up   View a printable version of the current page.  
  News Items for May, 2005
  2005/05/07
Last changed: Nov 19, 2005 10:28 by Andrus Adamchik

M4 Milestone is available for download. While I was busy working on some of the future features offline and billing overtime consulting hours, we received a number of cool contributions. This release includes most of them (see below) and also a bunch of bug fixes.

Modeler Changes (courtesy of Garry Watkins).

CayenneModeler has a number of visual improvements and new functionality. First thing that you'll notice is new and updated icons (mostly "borrowed" from Eclipse). Second addition is Entity Toolbars. All entity-specific operations are pulled from various places (such as main toolbar) and put right at user's fingertips under the entity tabs. This greatly reduces confusion (what button means what) and minimizes mouse movements. Additional operations are added - "Create ObjEntity" from DbEntity, "Sync Dependent ObjEntities with DbEntity". Third new feature is history navigation. Try the right and left arrows on the main toolbar.

Mapping Relationships

Notice that the buttons in the lower right corner that were used to open relationship mapping dialogs for DB and Object relationships are gone. They are replaced with the info button ("i") on the entity toolbar. This maybe confusing if you are used to old buttons.

Antlib File (courtesy of Erik Hatcher)

Now all Cayenne Ant tasks can be imported at once using Antlib facility (requires Ant 1.6):

<typedef resource="org/objectstyle/cayenne/tools/antlib.xml">
  <classpath refid="classpath"/>
</typedef>

Derby/Cloudscape Adapter (courtesy of Tore Halset)

Derby (aka Cloudscape) database is a recent addition to the family of open source database engines. M4 includes a DbAdapter for Derby. New adapter passes all our unit tests, however we only tested it in embedded mode. If anyone could try it in a client-server mode and share the experience (and set up details) with us, it would fantastic.

Full list of changes:

New Features:

  • CAY-301, CAY-211 New Modeler features.
  • CAY-183 Adapter for Clouscape/Derby DB (Experimental. Only tested in embedded mode).
  • CAY-312 Antlib descriptor addition.

Bugs Fixed:

  • CAY-306/CAY-313 Fixing API and build process to be more friendly when running under JDK 1.5
  • CAY-297 deadlock between commit and external event
  • CAY-308 ObjectStore.processUpdatedSnapshots race condition with ObjectStore.resolveHollow
  • CAY-299 PostgreSQL selecting procedure results are processed incorrectly
  • CAY-305 hsqldb needs to ignore "db schema" namespace element on CREATE/DROP TABLE syntax
  • CAY-310 Calling <DataObject>.addTo<DataObject>List() with a null parameter crashes later in commitChanges()
  • CAY-277 ExpressionFactory.noMatchDbExp does not exist
Posted at 07 May @ 10:49 PM by Andrus Adamchik | 0 comments
  2005/05/21
Last changed: May 22, 2005 12:15 by Andrus Adamchik

With Cayenne development schedule that we are trying to predict and maintain, some of the 1.2 features will come as a complete surprise even to me. I came across one of them just today when working on the new multi-tier design.

Certain operations in Cayenne are expressed internally using instances of Query. For example if a DataObject needs to inflate one of its to-many relationships, behind the scenes a SelectQuery is created and then passed to a DataContext for execution. For the client tier I wanted to use the same approach, however the queries had to be more lightweight and less dependent on the server-side mapping data. Unfortunately adding new kinds of queries to Cayenne caused a ripple effect all the way through the access stack, as DataNode needed to know how to process them. If you multiply this by 11 kinds of DbAdapters that we currently have, things got really ugly. So adding new query types was certanly a bad idea.

The solution turned out to be much easier than I thought. With my current obsession with the Visitor pattern, in just a few hours I was able to refactor DataNode to match arbitrary queries with pluggable JDBC execution algorithms by adding SQLActionVisitor interface to the query package, and letting DbAdapter to provide its own visitor implementation. As a result I cleaned up a lot of garbage code from DbAdapters and DataNode, and now I will be able to build my special client tier queries. But it was a side effect of this change that will have the biggest consequences in improving Cayenne. We ended up having a user-level API for extending Cayenne query behavior.

Two main types of extensions that Cayenne users can build now are (1) specialized queries that can internally delegate the execution step to a standard Cayenne query and (2) custom queries that provide their own JDBC-level execution algorithm. Here is some examples (note that to run these you will need HEAD CVS code or nightly build starting from May 22, 2005 and newer).

First we create a convenience superclass to quickly build custom selecting queries:

AbstractSelectQuery
package query.ext;

import java.util.Collection;
import java.util.Collections;

import org.objectstyle.cayenne.query.AbstractQuery;
import org.objectstyle.cayenne.query.GenericSelectQuery;
import org.objectstyle.cayenne.query.SQLAction;
import org.objectstyle.cayenne.query.SQLActionVisitor;

/**
 * A convenience superclass for custom select queries.
 * 
 * @author Andrei Adamchik
 */
public abstract class AbstractSelectQuery extends AbstractQuery implements
        GenericSelectQuery {

    public abstract SQLAction toSQLAction(SQLActionVisitor visitor);

    public String getCachePolicy() {
        return GenericSelectQuery.NO_CACHE;
    }

    public int getFetchLimit() {
        return -1;
    }

    public Collection getJointPrefetches() {
        return Collections.EMPTY_SET;
    }

    public int getPageSize() {
        return -1;
    }

    public boolean isFetchingDataRows() {
        return true;
    }

    public boolean isRefreshingObjects() {
        return false;
    }

    public boolean isResolvingInherited() {
        return false;
    }
}

Now we can build a custom selecting query that returns a list of related objects for a given DataObject and a relationships name:

RelationshipQuery
package query.ext;

import org.objectstyle.cayenne.DataObject;
import org.objectstyle.cayenne.access.util.QueryUtils;
import org.objectstyle.cayenne.query.SQLAction;
import org.objectstyle.cayenne.query.SQLActionVisitor;
import org.objectstyle.cayenne.query.SelectQuery;

/**
 * A query that fetches related objects for a given DataObject. Demonstrates how a custom
 * query can delegate its execution to a "standard" query that Cayenne understands.
 * 
 * @author Andrei Adamchik
 */
public class RelationshipQuery extends AbstractSelectQuery {

    protected SelectQuery substituteQuery;

    public RelationshipQuery(DataObject object, String relationship) {
        // build a SelectQuery that Cayenne understands,
        this.substituteQuery = QueryUtils.selectRelationshipObjects(object
                .getDataContext(), object, relationship);
    }

    public Object getRoot() {
        // return the real root. Cayenne needs to know it to find the right database.
        return substituteQuery.getRoot();
    }

    public SQLAction toSQLAction(SQLActionVisitor visitor) {
        // delegate execution to the real query
        return substituteQuery.toSQLAction(visitor);
    }
}

As you see a custom query can be built in just a few lines of code and no changes to Cayenne. The trick is in implementing new Query interface method "toSQLAction". But lets go further and implement a query that returns a single data row with the information about the database. This time we need to provide both Query and SQLAction implementations:

DBInfoAction
package query.ext;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.objectstyle.cayenne.access.OperationObserver;
import org.objectstyle.cayenne.query.SQLAction;

/**
 * A custom SQLAction that returns a single row from the database that contains some of
 * the DB metadata.
 * 
 * @author Andrei Adamchik
 */
public class DBInfoAction implements SQLAction {

    protected DBInfoQuery query;

    public DBInfoAction(DBInfoQuery query) {
        this.query = query;
    }

    public void performAction(Connection connection, OperationObserver observer)
            throws SQLException, Exception {

        Map infoRow = new HashMap();

        DatabaseMetaData metaData = connection.getMetaData();

        // collect some metadata
        infoRow.put("DB.name", metaData.getDatabaseProductName());
        infoRow.put("DB.product.version", metaData.getDatabaseProductVersion());
        infoRow.put("DB.version", metaData.getDatabaseMajorVersion()
                + "."
                + metaData.getDatabaseMinorVersion());

        observer.nextDataRows(query, Collections.singletonList(infoRow));
    }
}
DBInfoQuery
package query.ext;

import org.objectstyle.cayenne.query.SQLAction;
import org.objectstyle.cayenne.query.SQLActionVisitor;

/**
 * @author Andrei Adamchik
 */
public class DBInfoQuery extends AbstractSelectQuery {

    /**
     * Create a DBInfoQuery with "root". Root can be any Cayenne root, such as DataObject
     * class or DataMap name.
     */
    public DBInfoQuery(Object root) {
        setRoot(root);
    }

    public SQLAction toSQLAction(SQLActionVisitor visitor) {
        // return custom action...
        return new DBInfoAction(this);
    }

}

... and now call the query ...

DataContext context = DataContext.createDataContext();
  DataMap map = context.getEntityResolver().getDataMap("map");

  // run our custom query as a regular select
  List infos = context.performQuery(new DBInfoQuery(map));
  System.out.println("DB INFO: " + infos.get(0));

DBInfoQuery works just like a regular Cayenne selecting query, without Cayenne knowing anything about its execution strategy. I can see lots of possibilities with this feature....

Posted at 21 May @ 11:07 PM by Andrus Adamchik | 0 comments
  2005/05/22
Last changed: May 22, 2005 17:55 by Andrus Adamchik

Fresh impressions about Tapestry from a recent project. Version used is 3.0.3. And surely it shows my WebObjects bias, and probably a few misunderstandings.

Things I like about Tapestry (things that make it like WO or better than WO):

  • It works. It is not Struts and has no JSP underneath. It helps you to get things done.
  • It has the right main abstraction (hierarchical components with bindings).
  • It uses OGNL for bindings.
  • With Spindle Eclipse plugin templates behave "half-compiled" (e.g. if you rename a class, you'll see a red mark in a bindings file telling you to fix the class name; however if you rename a getter or setter method, Spindle doesn't detect that).
  • It allows bindings straight in HTML template.
  • It can attach a dynamic component to an arbitrary HTML tag using "jwcid" attribute. So there is no <tapestry> tag.
  • It has a Block component (a WOSwitchComponent analog on some serious steroids).
  • Highly configurable "application stack" (Engine, EngineService, Visit).

Things I don't like about Tapestry. Some of these have negative effect on productivity and design, some just on my mental health. I believe many (but not all) of the items below will be fixed in Tapestry 4.0.

  • Life-cycle method names are confusing (e.g. "initialize()" is not really "initialize" )
  • This bites all WO developers. For some mysterious reason page instances (instead of just templates) are pooled and reused across requests. Therefore whenever you "enter" a page to render a response, you have to completely reinitialize its state. Forget WO transparency in storing page state (and handling back buttons). As a result you'll have to write code for things you'd get in WO for free (ok, for $700 ). Page state handling is a topic of heated discussions on tapestry-user list. Clearly other users are not satisfied with it either.
  • Core component library has a number of inconsistencies and workarounds (I think this part will actually be improved soon). E.g. there is a repetition component called "Foreach", but you can't use it if you are iterating over the form fields. You'll need to use "ListEdit" component with an arcane model interface.
  • No WOBuilder analog. I like Spindle, but at least a minimalistic WYSIWYG support would go a long way.
  • I had action methods in the form called in the middle of "rewind" phase (aka "takeValuesFromRequest"), when not all data is read from the form yet! I had to implement an ugly workaround that would wrap action processing code in a "Closure" inner class instance that is passed along with request cycle and executed later when the form bindings are fully processed.

And finally things that are "different", I am not yet sure whether they are good or bad:

  • At times I have a feeling I am coding in Swing (an analogy Howard often uses himself) - everything is compiled (good), but takes forever to code (bad). In other words it takes more time to write your program, but less time to debug.
  • Another WO gotcha. There is no "takeValuesFromRequest", instead Tapestry would rerun its analog of "appendToResponse" in "rewind" mode. It takes time to get used to it.
Posted at 22 May @ 12:13 AM by Andrus Adamchik | 0 comments

May 2005
Sun Mon Tue Wed Thu Fri Sat
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31        

May 21, 2005
May 21, 2005

Site powered by a free Open Source Project / Non-profit License (more) of Confluence - the Enterprise wiki.
Learn more or evaluate Confluence for your organisation.
Powered by Atlassian Confluence, the Enterprise Wiki. (Version: 2.2.9 Build:#527 Sep 07, 2006) - Bug/feature request - Contact Administrators