[Opensource] DBObject multichoice field idea
Lirian Ostrovica
lirian.ostrovica at senecac.on.ca
Mon Apr 7 11:09:46 PDT 2003
Hi,
This is a follow up to a thread I started a couple of weeks ago (messages at the bottom of this one)
The idea, in a few words, is to add a new kind of DBObject field that is the correspondent of a html multiple value box. This way, we will have a 100% correlation between a DBObject and a user FORM.
I absolutely needed this with my Registration Objects so I have build my own solution which I'm showing down here, in case someone would be interested.
Because I do not want to touch the existing Expresso code, I extended DBObject instead of editing it.
- the new field is called MultiChoice (for not being confused with existing MultiValued fields)
- a MultiChoice field represents a set of zero, one or more predefined (valid) values. (just like html multivalue list values)
- MultiChoice fields are DBObject 'virtual' fields, which means that they are not physically stored in the target table.
- the values of a MultiChoice field are stored in a separate assigned table, which would minimally have two columns : one for the foreign key and one for the selected values. So if a MultiChoice field has currently
three (selected) values, this will be presented by three rows in the assigned table. If the MultiChoice field has no values there will be no rows in the assigned table for the respective key.
The assigned table would look like this :
foreignkey1 value1
foreignkey1 value2
foreignkey1 value3
foreignkey2 value1
foreignkey2 value3
foreignkey3 value2
foreignkey3 value1
and so on
- In the assigned table, the column that contains the values is a Multi-Valued field, whose valid values are assigned in whatever manner you want. The name of this column (field), MUST be the same as the name of our
MultiChoice field.
- in setupFields() method adding a MultiChoice field would look like :
addVirtualField("contactTime", "varchar", 50, "Contact time"); // existing method
setAsMultiChoice("contactTime",
"com.codepassion.pizzapassion.dbobj.ContactTimeSelections", // the assigned table
"ExpUid"); // foreign key
- My class overrides basic DBObject methods like : setField(..), getField(.), add(), update(), delete(), getValidValues(.)
and setStatus(.). The only thing to notice is that when calling setField(..) in a MultiChoice field you should pass a MULTICHOICE_DELIMITER delimited string of the values, while getField(.) will return such a delimited
string of values.
- Of course some expresso code has to be modified in order to accommodate this class (or a modified DBObject)
Down here is the source code.
This is just a quick attempt so .. hit me
Lirian
package com.codepassion.expresso.dbobj;
import com.jcorporate.expresso.core.db.DBException;
import com.jcorporate.expresso.core.dbobj.*;
import com.jcorporate.expresso.core.misc.StringUtil;
import com.jcorporate.expresso.core.dataobjects.DataFieldMetaData;
import java.util.StringTokenizer;
import java.util.HashSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Vector;
/**
* This class adds support for the multiple choice fields.
* Chosen values of a multiple choice field, will practically be saved in another
* assigned database table.
*
*/
public abstract class MyDBObject extends DBObject {
/**
* delimiter for the multiple field values
*/
public static final String MULTICHOICE_DELIMITER = ";";
/**
*
*/
public static final String SELECTION_DBOBJECT = "selectionDBObject";
/**
*
*/
public static final String SELECTION_DBOBJECT_FOREIGN_KEY = "selectionDBObject_foreignKey";
/**
* A Set of multichoice field names of this DBObject.
*/
private static HashSet multiChoiceFields = null;
public MyDBObject() throws DBException { // must
super();
}
// ========================= new methods ================================
/**
* Sets the given fieldName as MultiChoice field.
*
* @param fieldName - field name
* @param selectionDBObjectName - full class name of the DBObject that will store
* the selected values
* @param key - the name of the foreing key field in the selectionDBObject
* (primary key in this DBObject)
*
* @throws DBException
*/
public void setAsMultiChoice(String fieldName, String selectionDBObjectName, String foreignKey) throws DBException {
if(!isField(fieldName)) {
throw new IllegalStateException("'" + fieldName + "' is not a field of '" + this.getName() + "'");
}
if (!isVirtual(fieldName)) {
throw new DBException("Only virtual fields can be set as MultiValues, and '" + fieldName + "' is not such a one");
}
else {
if (multiChoiceFields == null) multiChoiceFields = new HashSet();
multiChoiceFields.add(fieldName);
setAttribute(fieldName, SELECTION_DBOBJECT, selectionDBObjectName);
setAttribute(fieldName, SELECTION_DBOBJECT_FOREIGN_KEY, foreignKey);
}
}
/**
* Checks whether the field is MultiChoice.
*
* @param fildName
* @return
*/
public boolean isMultiChoice(String fieldName) throws DBException {
if (fieldName == null)
return false;
return multiChoiceFields.contains(fieldName);
}
/**
* Returns a HashSet of multichoice field names of this DBObject
*
* @return
*/
public HashSet getMultiChoiceFields() {
return multiChoiceFields;
}
/**
* Will store in DB
*
* @param fieldName
* @param values
*/
private void storeSelectedValues(String fieldName) throws DBException {
String values = getField(fieldName);
deleteSelectedValues(fieldName);
// value == "" means that we should simply delete
// (no value is selected in the corresponding form)
if (values.length() == 0) { // no selection values
return;
}
String selectionDBObjectName = getAttribute(fieldName, SELECTION_DBOBJECT).toString();
String foreignKey = getAttribute(fieldName, SELECTION_DBOBJECT_FOREIGN_KEY).toString();
DBObject selectionDBObject = loadDBObject(selectionDBObjectName);
// add new records from values
StringTokenizer stk = new StringTokenizer(values, MULTICHOICE_DELIMITER);
while (stk.hasMoreTokens()) {
String value = stk.nextToken();
selectionDBObject.clear();
selectionDBObject.setField(foreignKey, getField(foreignKey));
selectionDBObject.setField(fieldName, value);
selectionDBObject.add();
}
}
/**
* Reads selection values from database and sets them as this field's value.
*
* @param fieldName
* @return
*/
private String loadSelectedValues(String fieldName) throws DBException {
StringBuffer selectedValues = new StringBuffer();
// get the attached selectionDBObject
String selectionDBObjectName = (String)getAttribute(fieldName, SELECTION_DBOBJECT);
String foreignKey = getAttribute(fieldName, SELECTION_DBOBJECT_FOREIGN_KEY).toString();
SecuredDBObject selectionDBObject = loadDBObject(selectionDBObjectName);
selectionDBObject.setField(foreignKey, getField(foreignKey));
DBObject oneRecord = null;
// for each record with the current key
for (Iterator i = selectionDBObject.searchAndRetrieveList().iterator(); i.hasNext(); ) {
oneRecord = (DBObject)i.next();
selectedValues.append(oneRecord.getField(fieldName)).append(MULTICHOICE_DELIMITER);
}
// store as this field's value
setMultiChoiceField(fieldName, selectedValues.toString());
return selectedValues.toString();
}
/**
*
* @param fieldName
* @return
* @throws DBException
*/
private void deleteSelectedValues(String fieldName) throws DBException {
String values = getField(fieldName);
// The attributes for fields are static across all DBObjects of a particular type.
DBObject selectionDBObject = loadDBObject(getAttribute(fieldName, SELECTION_DBOBJECT).toString());
String foreignKey = getAttribute(fieldName, SELECTION_DBOBJECT_FOREIGN_KEY).toString();
selectionDBObject.setField(foreignKey, getField(foreignKey));
DBObject oneRecord = null;
// for each record with the current uid
for (Iterator i = selectionDBObject.searchAndRetrieveList().iterator(); i.hasNext(); ) {
oneRecord = (DBObject)i.next();
oneRecord.delete();
}
}
/**
* Code here is part of the super.setField
* I suppose that MultiChoice fields are always strings (varchar)
*
* @param fieldName
* @param fieldValue
*/
private void setMultiChoiceField(String fieldName, String fieldValue) {
StringUtil.assertNotBlank(fieldName, "Field name may not be blank");
DataFieldMetaData oneField = getFieldMetaData(fieldName);
clearError(fieldName); // ?????
if (oneField.allowsNull() && (fieldValue == null)) {
/* it's already null */
} else {
// truncate strings if necessary
if (oneField.isQuotedTextType()
&& (oneField.getLengthInt() > 0)
&& (fieldValue != null)
&& (fieldValue.length() > oneField.getLengthInt())
) {
fieldValue = fieldValue.substring(0, oneField.getLengthInt());
}
}
logChange((DBField)oneField, fieldValue); // ????
setFieldData(oneField.getName(), fieldValue);
}
// =========================== Overriding methodds ========================
/**
* Overriding setField
*
* @param fieldName
* @param fieldValue
* @throws DBException
*/
public synchronized void setField(String fieldName, String fieldValue)
throws DBException {
if (!isMultiChoice(fieldName)) {
super.setField(fieldName, fieldValue);
}
else { // is multivalues field
setMultiChoiceField(fieldName, fieldValue);
}
}
/**
* Overriding getField
*
* @param fieldName Name of the field to fetch
* @return The value of the given field as a string - if the field is null,
* an empty string is returned.
* @throws DBException If there is no such field or it's value cannot be accessed
*/
public synchronized String getField(String fieldName)
throws DBException {
if (isMultiChoice(fieldName)) {
DataFieldMetaData oneField = getFieldMetaData(fieldName);
return StringUtil.notNull(getFieldData(oneField.getName()));
}
else { // is multivalues field
return super.getField(fieldName);
}
}
/**
* Overriding add()
* @throws DBException
*/
public void add() throws DBException {
if (multiChoiceFields != null) {
// for each multiChoiceField fieldName
for (Iterator i = multiChoiceFields.iterator(); i.hasNext(); ) {
String fieldName = (String)i.next();
storeSelectedValues(fieldName);
}
}
super.add();
}
/**
* Overriding update
*
* @throws DBException
*/
public void update() throws DBException {
if (multiChoiceFields != null) {
// for each multiChoiceField fieldName
for (Iterator i = multiChoiceFields.iterator(); i.hasNext(); ) {
String fieldName = (String)i.next();
storeSelectedValues(fieldName);
}
}
super.update();
}
/**
* Overriding update
*
* @throws DBException
*/
public void delete() throws DBException {
if (multiChoiceFields != null) {
// for each multiChoiceField fieldName
for (Iterator i = multiChoiceFields.iterator(); i.hasNext(); ) {
deleteSelectedValues((String)i.next());
}
}
super.delete();
}
/**
* Overriding getValidValues
*
* You have to have a field just for labels in the lookup object.
*
* @param fieldName
* @return
* @throws DBException
*/
public synchronized Vector getValidValues(String fieldName)
throws DBException {
if (isMultiChoice(fieldName)) {
// name of selectionDBObject for this field
String selectionDBObjectName = (String)getAttribute(fieldName, SELECTION_DBOBJECT);
SecuredDBObject selectionDBObject = loadDBObject(selectionDBObjectName);
// by default, this will automatically call the getValues() method of the
// object that acts as a lookupObject for the selectionDBObject field
// with the given name.
// But selectionDBObject can also implement its own
// getValidValues(String fieldName) method
return selectionDBObject.getValidValues(fieldName); // keep the same field name through tables
}
return super.getValidValues(fieldName);
}
/**
* Overriding setStatus
*
* I might find a better place(s) to do this
*
* @param newStatus <p>NEW: Record is new</p>
* <p>CURRENT: Record is synchronized with the database </p>
*/
protected synchronized void setStatus(String newStatus) {
if (newStatus.equalsIgnoreCase(DBObject.STATUS_CURRENT) &&
(multiChoiceFields != null)) {
try {
// for each multiChoiceField fieldName
for (Iterator i = multiChoiceFields.iterator(); i.hasNext(); ) {
String fieldName = (String)i.next();
loadSelectedValues(fieldName);
}
}
catch (DBException ex) {
// this is the bad thing here, I can not throw exception
// log.error(ex.getMessage());
}
}
super.setStatus(newStatus);
}
/**
*
* @param request
* @param dbobj
* @return
* @throws ControllerException
*/
protected SecuredDBObject loadDBObject(String dbobj)
throws DBException {
SecuredDBObject db = null;
try {
db = (SecuredDBObject) Class.forName(dbobj).newInstance();
db.setRequestingUid(SecuredDBObject.SYSTEM_ACCOUNT);
db.setDBName(getDBName());
db.setLocale(getLocale());
} catch (Exception e) {
throw new DBException (
"Instantiate failed for database object " + dbobj, e);
}
return db;
}
}
larry hamel wrote:
> is there a nice way to generalize a solution? like all nice-to-have features, if you figure out a way to make it work for your app, and that way would work for others, consider contributing it back to the library.
>
> larry
>
> At 07:26 AM 3/24/2003, you wrote:
> >Hi Larry,
> >
> >This question is not simply my personal concern, as I can fix it in a customized (my own) way.
> >The point is that I think that this deserves to be an Expresso issue, which means that we ought to find an optimal and reusable solution.
> >
> >Again, in practical terms, an example of when the problem rises is:
> >
> >If users have to have some registration information that is expressed through a multiple choice box, we CAN NOT use an Epresso RegistrationDBObject to save this user registration information in the database.
> >As consequence the whole Expresso's user administration support becomes obsolete.
> >
> >As I explained previously, going deeper, I do not see this as an RegistrationDBObject issue, but I see it as a DBObject issue, because the there can be other cases in which one would like to automatically
> >map an HTML FORM to the database and vice versa.
> >
> >As I said before the automatic mapping HTML FORM - DBObject already exists in Expresso and it is nicely used in the case of RegistrationDBObjects, but it falls short if the Html Form contains one ore more
> >multiple choice boxes.
> >
> >Thank you
> >
> >Lirian
> >
> >
> >
> >larry hamel wrote:
> >
> >> perhaps I don't understand the question, but consider an association table
> >>
> >> definition_table # this defines the pull down list
> >> poss_values
> >>
> >> association_table
> >> user_id
> >> chosen_value # foreign key on poss_values
> >>
> >> so long as there is no unique key which includes both these fields in in association table, multiple rows can be written for the same user_id (it can be other than user_id--whatever association you need)
> >>
> >> you mentioned "multi-valued" dbobjects, but have you seen MultiDBObject.java? it may apply here.
> >>
> >> larry
> >>
> >> At 06:27 AM 3/21/2003, you wrote:
> >> >Hi Dave and Mike,
> >> >Thanks for the quick answers.
> >> >Back to the same issue, my concern is in fact on the persistence layer (I have
> >> >already resolved on my own way the presentation and the form data collection,
> >> >much like you guys did)
> >> >
> >> >My question is :
> >> >How do I store into database the field values coming from a multiple select
> >> >MenuBox.
> >> >What kind of DBObject field would be able to do that ?
> >> >
> >> >In other words :
> >> >A DBObject record (or instance) can be presented in a HTML FORM, but the way
> >> >around is not always possible.
> >> >If the html form contains a multiple select box or a group of checkboxes, I can
> >> >not represent it as a DBObject field.
> >> >
> >> >In my practical case, I have some Registration Info for my users that consist of
> >> >multiple selection, and I can not use the
> >> >Expresso Registration Object for them.
> >> >
> >> >My first thoughts have been about either playing with a regular DBObject field,
> >> >whose value will be a delimiter separated string of the selected values, or
> >> >playing with the multi-valued DBObject fields.
> >> >
> >> >looking forward to any idea or opinion
> >> >
> >> >Lirian
> >> >
> >> >
> >> >
> >> >Michael Rimov wrote:
> >> >
> >> >> At 10:52 AM 3/20/2003 -0500, you wrote:
> >> >> >Expreso provides for automatically generating a HTML FORM FIELD, that would
> >> >> >correspond to a DBObject FIELD, and be used to update the field value.
> >> >> >A typical example is the generation of HTML forms for Registration Objects.
> >> >> >
> >> >> >I need that one field of my registration information be a multiple choice
> >> >> >MenuBox.
> >> >> >In DBObject terms this would mean that I need a multi-valued field, whose
> >> >> >value
> >> >> >be A SET of some of those valid values.
> >> >> >DBObject multi-valued fields go fine with html MenuBoxes that allow only a
> >> >> >single choice (multiple=false), but how about the multiple choices ?
> >> >> >So my question is :
> >> >> >1 - Does Expresso have any support for this kind of field. (I looked a bit
> >> >> >through the source code and could not find any clue).
> >> >> >2 - If not, has anyone used any work around to this.
> >> >>
> >> >> Lirian,
> >> >>
> >> >> Multiple selects in menu boxes are, indeed, not directly
> >> >> supported. Multiple checkboxes can be supported by giving each checkbox a
> >> >> unique ID number... I've done it that way in a couple of projects, and
> >> >> although the jsp is a little weird it makes the code on the expresso side
> >> >> much easier.
> >> >>
> >> >> To deal with multiple selections of the same name, you'll need to downcast
> >> >> ControllerRequest to ServletControllerRequest so you can get a List of all
> >> >> parameters, not just a Map of them.
> >> >>
> >> >> HTH!
> >> >> -Mike
> >> >>
> >> >> _______________________________________________
> >> >> Opensource mailing list
> >> >> Opensource at jcorporate.com
> >> >> http://mail.jcorporate.com/mailman/listinfo/opensource
> >> >> Archives: http://mail.jcorporate.com/pipermail/opensource/
> >> >
> >> >_______________________________________________
> >> >Opensource mailing list
> >> >Opensource at jcorporate.com
> >> >http://mail.jcorporate.com/mailman/listinfo/opensource
> >> >Archives: http://mail.jcorporate.com/pipermail/opensource/
> >>
> >> _______________________________________________
> >> Opensource mailing list
> >> Opensource at jcorporate.com
> >> http://mail.jcorporate.com/mailman/listinfo/opensource
> >> Archives: http://mail.jcorporate.com/pipermail/opensource/
> >
> >_______________________________________________
> >Opensource mailing list
> >Opensource at jcorporate.com
> >http://mail.jcorporate.com/mailman/listinfo/opensource
> >Archives: http://mail.jcorporate.com/pipermail/opensource/
>
> _______________________________________________
> Opensource mailing list
> Opensource at jcorporate.com
> http://mail.jcorporate.com/mailman/listinfo/opensource
> Archives: http://mail.jcorporate.com/pipermail/opensource/
More information about the Opensource
mailing list