<?xml version="1.0"?>
<!--
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements.  See the NOTICE file
 distributed with this work for additional information
 regarding copyright ownership.  The ASF licenses this file
 to you under the Apache License, Version 2.0 (the
 "License"); you may not use this file except in compliance
 with the License.  You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing,
 software distributed under the License is distributed on an
 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 KIND, either express or implied.  See the License for the
 specific language governing permissions and limitations
 under the License.
-->

<document>

  <properties>
    <title>Extend User Howto</title>
  </properties>

  <body>

    <section name="Important note">
      <p>
        The information in this HOWTO pertains to Turbine 2.2.  Please refer
        to the <a href="../services/security-service.html">Torque
        Security Service</a> page for information on extending TurbineUser
        in Turbine 2.3 and beyond.
      </p>
    </section>
    <section name="Introduction">
      <p>
        This is a HOWTO on extending the TurbineUser and its
        functionality.  The motivating factors for extending TurbineUser
        are:
        <ol>
          <li>
            to be able to make use of TURBINE_USER.USER_ID as a foreign key in
            application tables; and
          </li>
          <li>
            to be able to represent additional user attributes by adding columns
            to TURBINE_USER.
          </li>
        </ol>
      </p>
      <p>
        The example herein uses a very simple object model with only one table.
        This table would be defined in your project-schema.xml file.
        To illustrate solutions to both of our motivators we will:
        <ol>
          <li>
            Add a REVIEWED_BY_USER_ID column to the BOOK application table.  This will
              also include a foreign key reference back to TURBINE_USER.
          </li>
          <li>
            Add a TITLE column to TURBINE_USER.
          </li>
        </ol>
      </p>
      <p>
        <b>Important Note:</b> This solution is functionally incomplete in that
        it does not support the ability to use TurbineUser as a commit point for
        a transaction.  See the very end of this document for further details.
      </p>

      <p>
          It is also important to note that this HOWTO is intended for use with
          the database implementation of the SecurityService.  I will not address
          how to extend TurbineUser for use with any other implementation.
      </p>
    </section>

    <section name="How does TurbineUser actually work?" >

        <p>
            The inplementation of TurbineUser and TurbineUserPeer is distributed
            with Turbine in the <code>org.apache.turbine.om.security</code>
            package.  It is _NOT_ generated by Torque at this time.  There are
            no corresponding Base* classes nor a TurbineUserMapBuilder either.
        </p>
        <p>
            TurbineUser implements the User interface (from the same package).
            This interface is used by the Security Service and a few other
            services as well.  This is the reason behind not generating TurbineUser
            through Torque.  It would have no idea how to provide an implementation
            of the User interface leaving that inplementaion to you.
        </p>
        <p>
            The MapBuilder for TurbineUser is
            <code>org.apache.turbine.util.db.map.TurbineMapBuilder</code>.  This
            MapBuilder is also used by Turbine for the other Turbine* classes
            that are defined in the turbine-schema.xml file distributed with Turbine.
        </p>
        <p>
            The turbine-schema.xml file is the source of a small problem.  The ant task
            used to generate your OM layer will generate OM objects for all of the tables
            defined in this file along with the MapBuilders.  These classes do not really
            hurt anything but they can be a source of confusion.  There are not used
            by Turbine for anything!
        </p>
        <p>
            One valid reason for leaving this file in place is for the ant task that will
            create your database schema for you.  Since the task will not modify the schema
            for you after it is created (to add/remove columns, indexes, etc), it is of
            little use after the database is created other than to serve as a reference.
        </p>
        <p>
            Later in this discussion, you will be given instruction to rename this file to
            prevent it from being used by the ant tasks as well as removing any objects
            generated as a result.
        </p>
        <p>
            Another interesting fact about TurbineUser is the way in which data stored in
            the database is accessed.  Instead of using using private attributes for storage
            within the object, all attibutes are stored in a hashtable (known herein as the
            perm hashtable).  Access to the perm hashtable is controlled through the
            getPerm/setPerm methods.
        </p>
        <p>
            When the object is returned from the database, data from the columns are
            added to the perm hashtable for you.  When you save the object, the data
            is removed from the perm hashtable and written to the appropriate database
            columns.
        </p>
        <p>
            As you will read later, any data left in the perm hashtable that does
            not have a mapped database column (more on this later)
            during the save operation will be serialized and written to the
            TURBINE_USER.OBJECTDATA column.
        </p>
        <p>
            Another very nice feature of TurbineUser is the getTemp/setTemp
            methods.  This allows you to store data in TurbineUser for the duration
            of the session.  It will not be written to your persistent storage.
            It is ONLY for the session.  Keep in mind that the login action will
            replace the user object in the session with a user object from your
            persistent storage thus removing any data you might have stored in the
            user.
        </p>

    </section>

    <section name="Modifications to project-schema.xml">
      <p>
        Here is the sample project-schema.xml file before making modifications
        to reference TURBINE_USER.
      </p>
      <source>
<![CDATA[
<database defaultIdMethod="native" defaultJavaType="object">
    <table name="BOOK">
        <column name="BOOK_ID" required="true" primaryKey="true" type="INTEGER"/>
        <column name="TITLE" required="true" type="VARCHAR"/>
        <column name="AUTHOR" required="true" type="VARCHAR"/>
    </table>
</database>
]]>
      </source>
      <p>
        First we update the schema to include an alias definition of TurbineUser
        as well as the desired foreign key
        references.  Note how the TurbineUser definition refers to a pair of
        adapter classes (we will create these shortly) and how it is only
        necessary to define the columns we are referring to as foreign keys
        elsewhere in the application database.  Note also the addition of
        REVIEWED_BY_USER_ID as a foreign key in the BOOK table.
      </p>
      <source>
<![CDATA[
<database defaultIdMethod="native" defaultJavaType="object">
    <table name="EXTENDED_USER" alias="TurbineUser"
            baseClass="org.mycompany.sampleapp.om.TurbineUserAdapter"
            basePeer="org.mycompany.sampleapp.om.TurbineUserPeerAdapter">
        <column name="USER_ID" required="true" primaryKey="true" type="INTEGER"/>
    </table>
    <table name="BOOK">
        <column name="BOOK_ID" required="true" primaryKey="true" type="INTEGER"/>
        <column name="TITLE" required="true" type="VARCHAR"/>
        <column name="AUTHOR" required="true" type="VARCHAR"/>
        <column name="REVIEWED_BY_USER_ID" type="INTEGER"/>
        <foreign-key foreignTable="EXTENDED_USER">
            <reference local="REVIEWED_BY_USER_ID" foreign="USER_ID"/>
        </foreign-key>
    </table>
</database>
]]>
      </source>
        <p>
            Notice the attribute on the database tag for defaultJavaType.  I used
            "object" as the value.  The default is "primitive".  You do not have to
            use "object"!!!
        </p>
        <p>
            Turbine Version 2.1 only: In this version of Turbine, the only option was to use the
            primitive types.  However, this posed a small problem.  It was not
            possible to have number or boolean types that contained null values.
            If a null value was found in the database for columns of these types,
            the value returned from the OM object was 0 or false, respectively.
            This also implies that you can not have non-required foreign key
            references back to TURBINE_USER.  The primary key of TURBINE_USER
            is a number.
        </p>
    </section>

    <section name="Deciding how additional data in Turbine User will be stored">
      <p>
          There are two ways in which additional data in your extended TurbineUser
          object can be stored.  The first, and simplest method, is to store it in
          the perm hashtable.  As described earlier, any data in the perm hashtable
          not mapped to a database column will get serialized into the
          TURBINE_USER.OBJECTDATA column.  The second, and perferred method, is to
          use additional database column(s) for storage.
      </p>
      <p>
          Although storing the additional data in the TURBINE_USER.OBJECTDATA
          column is the simplest method, it has a few drawbacks.
          <ol>
              <li>
                  You will not have easy access to the data through SQL.
              </li>
              <li>
                  You will not be able to specify criteria to select TurbineUser
                  objects from the database by filtering on data stored in
                  TURBINE_USER.OBJECTDATA.
              </li>
              <li>
                  There is an upper limit to the amount of data that can be stored
                  in the TURBINE_USER.OBJECTDATA column.  A few users have reported
                  on the mailing list that they ran into this problem.  The limit
                  will vary from one database to another.  This limit is reduced
                  by persistent pull tools (see the docs on the PullService for
                  details), if you have any in your project,
                  as they are stored using this method by the PullService.
              </li>
          </ol>
      </p>
      <p>
          If you do decide to store some of your additional attributes
          in the TURBINE_USER.OBJECTDATA column, you need to be careful
          of possible conflicts with the keys used in the perm hashtable by
          TurbineUser.  When
          the object is saved to the database, this data is removed from the
          perm hashtable and written to the correct columns.  The keys used in the
          perm hashtable are the uppercase names of the columns.  The column names
          are not fully qualified with the names of the table like
          <code>TURBINE_USER.LAST_NAME</code>.  Instead the key used is
          <code>LAST_NAME</code>.  See
          <code>org.apache.turbine.util.db.map.TurbineMapBuilder</code> for a
          complete list of the keys used for the default columns.
      </p>
      <p>
          If you choose to use additional database columns, you will not have
          any of the drawbacks mentioned above.  Using the additional columns
          is the approach that should be taken unless you have a
          <strong>VERY</strong> good reason not to do so.
      </p>
    </section>

    <section name="Implementing adapter and map builder classes">
        <p>
            First, we will extend the map builder used by Turbine for the Turbine*
            classes to add the additional column(s) that we will be using in
            TURBINE_USER.
        </p>
        <source>
<![CDATA[
package org.mycompany.sampleapp.util.db.map;

import org.apache.torque.Torque;
import org.apache.torque.map.TableMap;
import org.apache.turbine.util.db.map.TurbineMapBuilder;

/**
 * Used to add the mapping of additional columns into any of the
 * TURBINE_* tables.  This can be implemented as an empty class
 * if you are not adding any additional database columns.
 */
public class TurbineMapBuilderAdapter extends TurbineMapBuilder
{
    /*
    * Note: The getUser_*() methods in this class should be static, but
    * getTableUser() and the initial field level methods are incorrectly
    * declared in TurbineMapBuilder and hence we must use non-static methods
    * here.
    */

    /**
     * Gets the name of the database column that we are adding to
     * TURBINE_USER.
     * @return The name of the database column.
     */
    public static String getTitle()
    {
        return "TITLE";
    }

    /**
     * Gets the fully qualified column name (TABLE.COLUMN) that will be
     * used to store the Title attribute in the extended TurbineUser object.
     * @return The fully qualified database column name.
     */
    public String getUser_Title()
    {
        return getTableUser() + '.' + getTitle();
    }

    /**
     * Creates the map used by Torque to persist the Turbine* objects
     * to the TURBINE_* tables.
     * @throws Exception generic error
     */
    public void doBuild() throws Exception
    {
        // the superclass version of the MapBuilder must be called to create
        // the mappings for the default columns.
        super.doBuild();

        // When you add a column to the database map, the map must know
        // what type of data will be stored in the column.  For that
        // purpose, we will create a few dummy objects to serve as
        // data type indicators.  Not all of them are used in our example.
        Integer dummyInteger = new Integer(0);
        Date dummyDate = new Date();
        String dummyString = new String();

        // Add extra column.
        TableMap tMap = Torque.getDatabaseMap().getTable(getTableUser());
        tMap.addColumn(getTitle(),dummyInteger);
    }
}
]]>
      </source>
      <p>
          Now we will implement the pair of adapters we referred to in our
          schema.  First we implement TurbineUserAdapter to provide access
          to the primary key as well any additional attibutes we are adding.
      </p>
      <source>
<![CDATA[
package org.mycompany.sampleapp.om;

import org.apache.turbine.om.security.TurbineUser;
import org.apache.torque.om.NumberKey;
import org.apache.turbine.util.ObjectUtils;

/**
 * This class extends TurbineUser for the purpose of adding get/set methods
 * for accessing additional attributes.
 */
public class TurbineUserAdapter extends TurbineUser
{
    /** Used as the key in the perm hashtable.  */
    public static final String TITLE = "TITLE";

    /**
     * Gets the userId of the user.  This is also the primary key
     * of the TURBINE_USER table.  The return type of Integer is
     * valid only if the javaType for the ExtendedUser table
     * is "object".  If it is "primative", the return type
     * should be changed to int.
     * @return the user id
     */
    public Integer getUserId()
    {
        return new Integer(((NumberKey)getPrimaryKey()).intValue());
    }

    /**
     * Sets the title of the user.
     * @param title the user's title
     */
    public void setTitle(String title)
    {
        setPerm(TITLE, title);
    }

    /**
     * Gets the user's title
     * @return the user's title
     */
    public String getTitle()
    {
        String tmp = null;
        try
        {
            tmp = (String) getPerm(TITLE);
            if ( tmp.length() == 0 )
            tmp = null;
        }
        catch (Exception e)
        {
        }
        return tmp;
    }
}
]]>
      </source>
      <p>
          Note: This class uses the setPerm and getPerm methods to
          access the data even though we are going to use the TURBINE_USER.TITLE
          column for storage.  As mentioned earlier, the data is removed from the
          hashtable when the object is saved to the database and written to the
          correct database column.  Likewise, the when the object is retrieving
          data from the database, the data from the TURBINE_USER.TITLE column is
          added to the perm hashtable with the TITLE key.
      </p>
      <p>
          Next comes TurbineUserPeerAdapter to which we add details of the new
          database columns.
      </p>
      <source>
<![CDATA[
package org.mycompany.newapp.om;

import org.mycompany.sampleapp.util.db.map.TurbineMapBuilderAdapter;
import org.apache.torque.om.ObjectKey;
import org.apache.torque.util.Criteria;
import org.apache.turbine.om.security.peer.TurbineUserPeer;

/**
 * This class extends TurbineUserPeer for the purpose of mapping additional
 * database columns.  You can implement this as an empty class if you are not
 * using any additional database columns.
 */
public class TurbineUserPeerAdapter extends TurbineUserPeer
{
    /** the default database name for this class */
    public static final String DATABASE_NAME = "default";

    /** Used to build the map for the extended Turbine User */
    private static final TurbineMapBuilderAdapter mapBuilder =
        (TurbineMapBuilderAdapter)getMapBuilder("org.mycompany.sampleapp.util.db.map.TurbineMapBuilderAdapter");

    /** The fully qualified name of the database table */
    public static final String TITLE = mapBuilder.getUser_Title();

    /**
     * Builds a criteria object to select by a primary key value.  Of course,
     * it could also be used for an update or delete.
     * @param pk Primary key to select/update/delete
     * @return A Criteria object built to select by primary key
     */
    public static Criteria buildCriteria(ObjectKey pk)
    {
        Criteria crit = new Criteria();
        crit.add(TurbineUserPeer.USER_ID, pk.getValue());

        return crit;
    }
}]]>
      </source>
    </section>

    <section name="Generating the OM layer">
        <p>
            Before generating the OM layer, see if you have a turbine-schema.xml
            file in your WEB-INF/conf directory.  If you do, rename this file to
            turbine-schema.xml.nogenerate.  Also, the only Turbine* classes that
            you should have in your om package are the two adapters that you
            created earlier.  Any other Turbine* or BaseTurbine* class in your om
            package should be removed.  You should also remove any om.map.Turbine*
            classes that might have been generated.
        </p>
        <p>

            <b>Warning:  Do not run the init ant task after doing this.</b>
            That task will drop and recreate your database.  Without the
            turbine-schema.xml file present, the TURBINE_* tables will not
            be recreated!
        </p>
        <p>
            These classes were generated from the turbine-schema.xml file.  The
            project-om ant task will process
            any file in your WEB-INF/conf directory ending in <code>-schema.xml</code>
            and generate the appropriate classes from that definition.  Renaming the
            turbine-schema.xml file stops these extra classes from being generated.
        </p>
        <p>
            Next, you will need to manually create any additional database columns
            that you will be using in TURBINE_USER.  Make sure that the name of the
            columns match what you speificied in TurbineUserPeerAdapter.
        </p>
        <p>
            We can now use project-om ant task to generate our OM layer using
            the adapter classes we defined above.
        </p>
        <p>
            With any luck everything will compile okay and we are only a small step
            away from being able to use the new OM layer.
        </p>
    </section>

    <section name="Modifications to TurbineResources.properties">
        <p>
            The last step is to tell Turbine about the new classes we are using for
            Users and MapBuilder.  To do this we need to update the following
            entries in TurbineResources.properties:
        </p>
      <source>
<![CDATA[
database.maps.builder=org.mycompany.sampleapp.util.db.map.TurbineMapBuilderAdapter
services.SecurityService.user.class=org.mycompany.sampleapp.om.ExtendedUser
services.SecurityService.userPeer.class=org.mycompany.sampleapp.om.ExtendedUserPeer
]]>
      </source>
        <p>
            That is basically it.  We can now modify our application to utilise the
            new columns via the methods defined in the OM objects we have modified.
            Note that in order to access the new methods in ExtendedUser we need to cast
            from TurbineUser thus:
        </p>
      <source>
<![CDATA[
ExtendedUser user = (ExtendedUser) data.getUser();
]]>
      </source>
        <p>
            Enjoy.
        </p>
    </section>

    <section name="Additional Information">
        <p>
            For those of you that are attempting to extend TurbineUser to make use of
            TURBINE_USER.USER_ID as a foreign key in your application tables here is
            one small problem.
        </p>
        <p>
            Torque generates some pretty useful code that enables you to add related
            objects to a common parent object and then save them all to the database
            in one transaction.  You can do something like this:
        </p>
        <source>
<![CDATA[
OrderDetail od1 = new OrderDetail(500, "item1");
OrderDetail od2 = new OrderDetail(100, "item2");
Order o = new Order(clientId);
o.addOrderDetail(od1);
o.addOrderDetail(od2);
o.save();
]]>
        </source>
        <p>
            This is very helpful - you don't need to worry about the ids that connect
            the child rows to the parent rows and you don't need to worry about the
            transaction necessary to insert these into the database as one atomic
            operation.  It also provides:
        </p>
        <source>
<![CDATA[
o.save(dbConn);
]]>
        </source>
            <p>
                which can be used when you have other data you want to commit in a
                transaction you are managing yourself.
            </p>
            <p>
                Now here is the problem.  After extending TurbineUser in the manner
                described above, the methods are generated to add records related the
                extended user class, but the data itself is ignored when
                ExtendedUser.save() is invoked.  Here is an example:
            </p>
        <source>
<![CDATA[
Book book = new Book();
data.getParameters().setProperties(book);
ExtendedUser user = (ExtendedUser) data.getUser();
user.addBook(book);
user.save(); // !!!! book is not saved !!!!
]]>
        </source>
            <p>
                Of course replacing the last two lines with:
            </p>
        <source>
<![CDATA[
book.setReviewedByUserId(user.getUserId());
book.save();
]]>
        </source>
            <p>
                will in fact save the Book, this is beside the point - the other method
                addBook() shouldn't exist if it doesn't work and this is a trivial example.
            </p>
            <p>
                In addition to this, there is no equivalent save(dbConn) method provided
                to allow this update to be combined with others within a single database
                transaction.
            </p>
            <p>
                There are a few solutions to this problem.  One would be to fix the torque
                template responsible for generating the code.  Another would be to implement
                the methods yourself.  Before exploring either of these options, you need to
                ask yourself if this problem will even affect you.  If not, don't worry about
                it.
            </p>
            <p>
                Implementing the methods yourself might be the simpliest route for someone
                new to Turbine and Torque.  This simply involves overriding the addBook()
                method in the example above with the two lines shown previously.  Of course,
                You would have to do this for each and every one that Torque generates
                for you.
            </p>
            <p>
                The same idea applies to the save(dbConn) method.  Simply override that method
                in your ExtendedTurbineUser class (or whatever you called your class) to perform
                the operation correctly.  You can get sample code from one of the other classes
                that Torque generated for you.
            </p>
            <p>
                If you want Torque to generate to code for you, you will have to modify the
                Object.vm template that Torque uses.
            </p>
            <p>
                The other solution involves altering a torque template so that the code that
                generates the aspects of the save() method that handle related tables is
                allowed to execute for the TurbineUserAlias class.  You need to apply the
                following patch (most likely to the version of Object.vm copied to the
                WEB-INF/build/bin/torque/templates/om directory for your application):
            </p>
        <source>
<![CDATA[
Index: jakarta-turbine-2/conf/torque/templates/om/Object.vm
===================================================================
RCS file: /home/cvspublic/jakarta-turbine-2/conf/torque/templates/om/Object.vm,v

retrieving revision 1.3
diff -u -r1.3 Object.vm
--- jakarta-turbine-2/conf/torque/templates/om/Object.vm        24 Oct 2001 18:1
2:21 -0000      1.3
+++ jakarta-turbine-2/conf/torque/templates/om/Object.vm        16 Jan 2002 14:0
0:43 -0000
@@ -787,7 +787,7 @@
#end     ## ends the if(addGetByNameMethod)


-#if (!$table.isAlias() && $addSaveMethod)
+#if ((!$table.isAlias() || $table.Alias == "TurbineUser") && $addSaveMethod)
/**
* Stores the object in the database.  If the object is new,
* it inserts it; otherwise an update is performed.
@@ -880,6 +880,9 @@
{
alreadyInSave = true;
#end
+    #if ($table.isAlias() && $table.Alias == "TurbineUser")
+        super.save();
+    #else
if (isModified())
{
if (isNew())
@@ -892,6 +895,7 @@
${table.JavaName}Peer.doUpdate(($table.JavaName)this, dbCon);
}
}
+    #end

#if ($complexObjectModel)
#foreach ($fk in $table.Referrers)
]]>
        </source>
            <p>
                You will need to manually remove the line wrapping from the above
                patch.  This patch is only included here to give you an idea of what
                would need to be changed in Torque.  The patch can not be applied
                to the current version of Torque simply because it is so outdated.
            </p>
            <p>
                Notice how although you can go <code>user.save(dbCon);</code> the
                TurbineUser record will not be part of the transaction.
            </p>
            <p>
                I would suggest that you override the methods that do not
                get generated correctly in your ExtendedTurbineUser class.  Do so ONLY
                if this problem will affect you.
            </p>
            <p>
                The real solution to this problem is in modifing Turbine in such a way to
                allow Torque to generate the TurbineUser object.  This would eliminate the
                need to extend TurbineUser altogether.  There would still be the need to add
                additional columns and/or create FK references to the TURBINE_USER table.  It
                would would only involve modifing the schema files and letting Torque do
                all of the work instead of creating the adapter classes and modifing the
                map builder.
            </p>
            <p>
                Note:  The last solution mentioned should be ready for Turbine 2.3.  This
                should make life easier for everyone!!!
            </p>

    </section>

  </body>
</document>
