Translate

Showing posts with label Struts 2. Show all posts
Showing posts with label Struts 2. Show all posts

Friday, November 9, 2012

jQuery - DataTables Server-side Sorting

I couldn't use the server-side sorting ability of DataTables and needed to implement my own. Here's how I did it:

First - turn off DataTables sorting by setting bSort to false.

2 - Add a hidden Div for the menu for my sorting:


<div id="tabmenu" style="visibility: hidden; display: none; position: absolute; z-index: 6" class="popTableMenu">
 <table cellspacing="0" cellpadding="0">
   <tr>
     <td>
       <a id="fpopSortDn" href="#" onclick="return inSort('asc')">Sort down</a>
       <a id="fpopSortUp" href="#" onclick="return inSort('desc')">Sort up</a>    
     </td>
   </tr>
 </table>
</div>


3 - Modify the column heading, adding "onmouseover" and "onclick" events:

<th style="height:26px;" class="heading" onmouseover="inMenuHide()" onclick="inMenuShow(this, '${fieldName}');" ><s:property value="fieldLabel"/></th>


4 - Add the JavaScript to handle the mouse events:

<script type="text/javascript"<
var tSort;

function inSort(direct){
 inMenuHide();
 showLoading();
 var mID = $('#${tablename}viewID').val();
 var mAct = $('#${tablename}listActionName').val();
 $('div#divBody').load('/InForm${namespace}/'+mAct+'.action?rpp=' + $('#resPerPage').val() + '&sortBy=' + tSort + '&sortOrder=' + direct + '&viewID=' + mID + '&search=' + $('#${tablename}fastSearch').val());
 return false;
}

function inMenuShow(cell, field){
 tSort = field;
 var offset = $(cell).offset();
 var cH = $(cell).height();
 var pT = offset.top + cH;
 $("#tabmenu").offset({ top: pT, left: offset.left });
 $('#tabmenu').css("visibility","visible");
 $("#tabmenu").show("fast");
}

function inMenuHide(){
 $("#tabmenu").offset({ top: 0, left: 0 });
 $("#tabmenu").hide();
 $('#tabmenu').css("visibility","hidden");
}
</script>

Clicking on a column heading will call "inMenuShow" so that the sort memu, the hidden Div, displays below the column:
Clicking on an item in the sort menu wil call the "inSort" method. This method calls my struts action and populates the table container, "divBody", with the returned page. The page that's returned ids the sorted table.

Friday, September 7, 2012

Struts - Include Causes BufferOverflowException

Using the Struts include tag on some pages is causing the following error:
java.nio.BufferOverflowException
at java.nio.HeapByteBuffer.put(Unknown Source)
at org.apache.struts2.util.FastByteArrayOutputStream.decodeAndWriteOut(FastByteArrayOutputStream.java:161)
at org.apache.struts2.util.FastByteArrayOutputStream.writeTo(FastByteArrayOutputStream.java:94)
at org.apache.struts2.components.Include.include(Include.java:285)
at org.apache.struts2.components.Include.end(Include.java:167)
at org.apache.struts2.views.jsp.ComponentTagSupport.doEndTag(ComponentTagSupport.java:42)
The only solution I could find so far is to replace the Struts include with a JSP include. Does anyone know how to fix this so it works in Struts?

Thursday, August 2, 2012

jQuery - Submit a Form Using an Anchor Tag

While you can use JavaScript in the onclick event of an anchor tag to submit a form, there may be times when you need to submit additional information along with the form. That's what I needed to do and this is how I did it using the jQuery method. This example is for Struts 2 but should work with a regular anchor tag.


<s:url var="urlFkey" namespace="%{namespace}" action="GotoAddForeignKeyRecord" escapeAmp="false" >
  <s:param name="target" >disp${fieldName}</s:param>
  <s:param name="fkeyName" value="fkeyName"/>
  <s:param name="fkeyTableName" value="fkeyPrimaryTableName"/>
  <s:param name="recordID" value="recordID"/>
</s:url>
<s:a id="disp%{fieldName}" href="%{urlFkey}" cssClass='nyroModal' onclick="this.href = this.href + '&' + $('#%{dispTablename}saveRecord').serialize();">
   Add New <s:property value="fkeyPrimaryTableNameLabel"/>
</s:a>

In the JSP snippet above I am calling the jQuery serialize method in the onclick event of the anchor tag and appending the returned result to the href for the tag.

Thursday, June 28, 2012

AJAX - Prevent Page Caching

There were a couple of little things that caught me this past. The first one was how Internet Explorer is so aggressive about caching and it's really annoying when it caches AJAX results. I decided to deal with it in my Struts action by adding a header to my response like this:

this.response.addHeader("Cache-Control", "max-age=0,no-cache,no-store,post-check=0,pre-check=0");

Friday, June 15, 2012

AJAX - Browser History

One of the issues with using AJAX in a web site is that the browser history does not work. I spent a few days trying out quite a few of the solutions that you can find on the web and I was unable to find one that worked consistently in my application. The only option left was to add my own history and back button to my application.

The script file, rgUrlHistory.js:

var rgUrlStack = new Array();
var rgIdx = 0;

function rgPushAction(mAct, mApp, mID, mType) {
  rgObj = [];
  rgObj.action = mAct;
  rgObj.appID = mApp;
  rgObj.type = mType;
  rgObj.id = mID;
  rgUrlStack.push(rgObj);
  //limit to 10 items
  if (rgUrlStack.length > 10) rgUrlStack.splice(0,1); 
  rgIdx = rgUrlStack.length;
}

function rgBackAction() {
  var rgPop;
  //if we are at the top of the stack we need to pop this value because the first value popped is the current URL
  if (rgUrlStack.length == rgIdx) rgUrlStack.pop();
  //if we still have a value, pop it
  if(rgUrlStack.length > 0){
    rgPop = rgUrlStack.pop();
    //always keep one value on the stack
    if(rgUrlStack.length == 0) rgUrlStack.push(rgPop);
  }
  return rgPop;
}


In my application I call rgPushAction to save my URL and some additional information:

onclick="rgPushAction('%{urlFkey}','%{appTableID}','%{ID}','%{currentAppTable.type}');

On the menu bar in my web application I added a left arrow icon and call the the following JavaScript when the image is clicked:

function runBackQuery(){
   $.publish('HideViewMenu');
   var rgObj = rgBackAction();
   if(rgObj != ""){
     listView(rgObj.type, rgObj.appID, rgObj.id)
     $.post(rgObj.action, function(result){
          $("div#divBody").html(result);
        });
     $.publish('ShowDivBody');
   }
}

This script uses jQuery post with the URL we saved returned in rgObj.action to populate our target Div tag, divBody. "listView" is just another function that is called with the additional information we saved in the rgObj object. That's all there is to doing your own browser history for your AJAX actions.

Sunday, April 1, 2012

jQuery, AJAX, Struts - Add a Select Box Option

Here is a quick little example on how to add a new option to a select box and make the new value the selected value.

On my JSP page I have two select boxes that can have new values added. When a new value for an Award or Project is add, the new value will be added as an option to the select box and made the selected value.


A modal dialog box is used to enter a new Award or Project. In this image, you can see a section of the dialog box for adding a new Award. In the dark area you will see a section of the original record edit screen.


Here is the section from the JSP page for the select box. Since the forms are generated dynamically, the id of my select box can have any value so I need to return that value to the server so that it can be used later on as the AJAX target. In the code snippet below you see this value being returned as the "target" parameter.

<s:select name="tableRecord.tblRecord.%{fieldName}" id="disp%{fieldName}" list="selectListMap(fkeyName)" headerKey="" headerValue="Please Select"/>
<s:if test="fkeyPrimaryTableName != null">
 <s:url var="urlFkey" namespace="%{namespace}" action="GotoAddForeignKeyRecord" escapeAmp="false">
  <s:param name="target" >disp${fieldName}</s:param>
  <s:param name="fkeyName" value="fkeyName"/>
  <s:param name="fkeyTableName" value="fkeyPrimaryTableName"/>
 </s:url>
 <s:a href="%{urlFkey}" cssClass='ajax'>
  Add New <s:property value="fkeyPrimaryTableNameLabel"/>
 </s:a>
 <script type="text/javascript">
 $(".ajax").colorbox({width:'800', close:'close'});
 </script>
</s:if>


I use XML files to define the fields for the database tables. Here is the section from the XML file that describes the awardID.

<FieldDescriptor>
    <fieldName>awardID</fieldName>
    <fieldLabel>Award</fieldLabel>
    <fieldType>1</fieldType>
    <fieldSize>0</fieldSize>
    <editable>true</editable>
    <listable>true</listable>
    <required>true</required>    
    <fkeyName>lstAwards</fkeyName>
    <fkeyPrimaryTableName>Award</fkeyPrimaryTableName>
    <fkeyPrimaryTableNameLabel>Award</fkeyPrimaryTableNameLabel>
    <fkeyListEditable>true</fkeyListEditable>
</FieldDescriptor>   


Don't get hung up on this XML structure or the information it contains. The import part follows, getting the new value into the select box.

Here is my GotoAddForeignKeyRecord action method. Notice that the target for my AJAX action is being saved as ajaxTarget.

public String gotoAddForeignKeyRecord(){
 String fkeyName = request.getParameter("fkeyName");
 //save the target for the AJAX action
 this.ajaxTarget = request.getParameter("target");
 this.baseTableDescriptor = TableDescriptorFactory.getFieldDescriptor(table); 
 for(KeyDescriptor kD: this.baseTableDescriptor.getLstKeyDescriptor()){
  if(kD.getName().equals(fkeyName)){
   String tMapper = kD.getMapper();
   if(kD.getTableMapper() != null) {
    tMapper = kD.getTableMapper();
   }
                        //luTableField is used for the display value in the select box on the JSP page
   this.luTableField = kD.getLuTableField();
   //set the Struts namespace for the table we are targeting
   this.redirectNamespace = "/" + tMapper.replace("Mapper", "").toLowerCase();
   //set the page that we will be redirecting to
   this.redirectPage = "AddForeignKeyRecord";
   this.recordID = null;
   return SUCCESS;
  }
 }
 return ERROR;
}


The section from the record add JSP page with the jQuery code for populating the select box. The values that I need for the key and value I get from the "${tablename}ID" and "disp${luTableField}" on the JSP page.

<s:url id="urlCancelRecordAdd" namespace="%{namespace}" action="NoOpp"/>
<sj:submit  value="Cancel" formIds="saveRecordFK" id="cancelFKRecordEdit" href="%{urlCancelRecordEdit}" onclick="parent.$.colorbox.close();return false;" button="true" style="font-size: .7em;"/>
</td>
</tr>
<script type="text/javascript">
//submit the list
$('#cancelFKRecordAdd').click(function() {
 //if we have an ID then we have a record so update the sellect box
 if($('#${tablename}ID').val() != ''){
        //make the markup for the option for the select box
 var option = '<option value=' + $('#${tablename}ID').val() + '>' + $('#disp${luTableField}').val() + '</option>';
        //add the option to the select box
 $('#${ajaxTarget}').append(option);
        //set the added option as the selected value
 $("#${ajaxTarget}").val($('#${tablename}ID').val());
 }
});
</script>

Sunday, March 11, 2012

Struts 2 - Dynamic Data Grid Example

The data grid shown below was dynamically created using Struts 2, MyBatis, and jQuery. XML files are used to define the structure of the table being edited and the table relationship of foreign key fields. The structure that is used to dynamically generate the final form can be easily adapted to fit any simple table. This structure gives me the ability to quickly add tables, add or remove table fields, and create a data grid that can be used to view and delete records and edit fields.

The SQL script to create the table.

CREATE TABLE [dbo].[cjxProjectStaff](
 [RecordID] [int] NULL,
 [AppUserID] [int] NULL,
 [StaffRoleID] [int] NULL,
 [BudgetAuthority] [bit] NULL
) ON [PRIMARY]

GO

ALTER TABLE [dbo].[cjxProjectStaff]  WITH CHECK ADD  CONSTRAINT [FK_cjxProjectStaff_AppUser] FOREIGN KEY([AppUserID])
REFERENCES [dbo].[AppUser] ([ID])
GO

ALTER TABLE [dbo].[cjxProjectStaff] CHECK CONSTRAINT [FK_cjxProjectStaff_AppUser]
GO

ALTER TABLE [dbo].[cjxProjectStaff]  WITH CHECK ADD  CONSTRAINT [FK_cjxProjectStaff_luStaffRole] FOREIGN KEY([StaffRoleID])
REFERENCES [dbo].[luStaffRole] ([ID])
GO

ALTER TABLE [dbo].[cjxProjectStaff] CHECK CONSTRAINT [FK_cjxProjectStaff_luStaffRole]
GO

ALTER TABLE [dbo].[cjxProjectStaff]  WITH CHECK ADD  CONSTRAINT [FK_cjxProjectStaff_Project] FOREIGN KEY([RecordID])
REFERENCES [dbo].[Project] ([ID])
GO


I use two XML files. One XML file defines the structure of the table and is used in the application to provide information on what fields to display, the types of fields, and whether or not the field can be edited. Here is the file that defines the fields in the ProjectStaff table:

<list>
  <FieldDescriptor>
    <fieldName>recordID</fieldName>
    <fieldLabel>Project ID</fieldLabel>
    <fieldType>1</fieldType>
    <fieldSize>0</fieldSize>
    <editable>false</editable>
    <listable>false</listable>
  </FieldDescriptor>
  <FieldDescriptor>
    <fieldName>appUserID</fieldName>
    <fieldLabel>Staff ID</fieldLabel>
    <fieldType>1</fieldType>
    <fieldSize>0</fieldSize>
    <editable>true</editable>
    <listable>true</listable>
    <fkeyName>lstKeyStaff</fkeyName>    
  </FieldDescriptor>
  <FieldDescriptor>
    <fieldName>StaffRoleID</fieldName>
    <fieldLabel>Role ID</fieldLabel>
    <fieldType>1</fieldType>
    <fieldSize>0</fieldSize>
    <editable>true</editable>
    <listable>true</listable>
    <fkeyName>lstStaffRole</fkeyName>     
  </FieldDescriptor>
  <FieldDescriptor>
    <fieldName>budgetAuthority</fieldName>
    <fieldLabel>Budget Authority</fieldLabel>
    <fieldType>5</fieldType>
    <fieldSize>0</fieldSize>
    <editable>true</editable>
    <listable>true</listable>    
  </FieldDescriptor>
</list>


When this file is read in to the application, it creates a List of type FieldDescriptor, describing all of the fields in the table and relationships to other tables if the field is a foreign key.

Another XML file defines tables that are related to the main table by a foreign key. The fkeyName field from the XML file above is used to link to the fields defined in the following XML file.

<list>
  <KeyDescriptor>
    <name>lstKeyStaff</name>
    <mapper>KeyValueDescriptionMapper</mapper>
    <method>selectAllKeyValue</method>
    <luTable>vwAllKeyStaff</luTable>
    <luTableField>DescValue</luTableField>    
  </KeyDescriptor>
  <KeyDescriptor>
    <name>lstStaffRole</name>
    <mapper>KeyValueDescriptionMapper</mapper>
    <method>selectAllKeyValue</method>
    <luTable>luStaffRole</luTable>
    <luTableField>DescValue</luTableField>    
  </KeyDescriptor>
</list>



When this file is read in to the application, it creates a List of type KeyDescriptor. The "KeyDescriptor" tells the application how to load in values to create a list of Key/Value pairs. The list will be used by Struts to create the select lists in our JSP page.

Now for the main part of the application. Besides my Struts action class, there are two classes with five main fields that are used.

public abstract class BaseTable {
protected List<Object> lstRecord;//list of records from the table
protected BaseTableDescriptor baseTableDescriptor;//the table descriptor for the table records that are being viewed or edited
protected HashMap<Object, Map<Object, String>> selectMap; //map of selection values for foreign key tables

public abstract class BaseTableDescriptor {
protected List<FieldDescriptor> lstFieldDescriptor;
protected List<KeyDescriptor> lstKeyDescriptor;

The list, lstRecord, is populated with the table records from the ProjectStaff table. The list, lstFieldDescriptor, is created from the XML file displayed above and as stated before this list describes the database fields.
The list, lstKeyDescriptor, is created from the XML file displayed above and the information in this list is used to create the selectMap HashMap. For each entry in "selectMap", the key is "KeyDescriptor.name" and the value is the list of Key/Value pairs for the named table.

This information is then used in the Struts tags in the following JSP page to create the table.

<!--Iterate through our list of records-->
<s:iterator value="baseTable.lstRecord"  var="element" status="stat">
<tr>
<!--For each record, iterate through our list of field descriptors-->
<s:iterator value="baseTable.baseTableDescriptor.lstFieldDescriptor" var="fdelement" status="fdstat">
<s:if test="listable">
  <s:if test="#stat.odd == true">
  <td style="background-color:#F9F9d9;white-space:nowrap;height:23px;">
  </s:if>
  <s:else>
  <td style="background-color:#F9F9F9;white-space:nowrap;height:23px;">
  </s:else>
  <s:if test="#fdstat.index == 1">
   <img id="imgDel${stat.index}" src="../images/del1.gif" style="border:0px" alt="Delete" title="Delete"/>
  <script type="text/javascript">
    //delete the record
    $('#imgDel${stat.index}').click(function() {
      //clear the value to mark the record for deletion
      $('#${fieldName}${stat.index}').val('');
      //hide the row
      $(this).parent().parent().hide();
    });
  </script>
  </s:if>
  <!--If this is a foreign key field-->
  <s:if test="fkeyName != null">
      <s:select name="baseTable.lstRecord[%{#stat.index}].%{#fdelement.fieldName}" id="%{#fdelement.fieldName}%{#stat.index}" list="baseTable.selectListMap(fkeyName)" headerKey="" headerValue="Please Select" theme="simple"/>
  </s:if>
  <s:else>
    <!--If this is a boolean field-->
    <s:if test="fieldType == 5">
      <s:checkbox name="baseTable.lstRecord[%{#stat.index}].%{#fdelement.fieldName}" theme="simple"  title="%{fieldLabel}"/>
    </s:if>
    <!--The following is just used for debugging-->
    <s:else>
      <s:if test="fkeyName != null">
        <s:property value="keyValue(fkeyName, '#element.' + #fdelement.fieldName)"/>
      </s:if>
      <s:else>
        ${element[fdelement.fieldName]}
      </s:else>
    </s:else>
  </s:else>
  </td>
</s:if>
</s:iterator>
</tr>
</s:iterator>
<tr>
  <s:if test="baseTable.lstRecord.size() < 5">
    <td id="tdAddNew" style="background-color:#a0F0d9;" colspan="1"> <img id="imgAddNew" src="../images/max1t.gif" style="border:0px"/> <b>Add New Project Staff</b></td>
    <script type="text/javascript">
    //add a new record
    $('#tdAddNew').click(function() {
      //clear the result message
      $("#tdResMsg").val("")
      //save the records and reload the table
      $("#${baseTable.ajaxTarget}").load('../${namespace}/AddCustomTableRecord.action?btName=${baseTable.tableName}&btRecID=${baseTable.recordID}&rtSize=${baseTable.lstRecord.size()}&rNew=true&target=${baseTable.ajaxTarget}', $("#saveRecord").serialize());
    });
    </script>
  </s:if>
  <s:else>
    <td colspan="1"><b> Max records allowed reached.</b></td>
  </s:else>
  <td id="tdResMsg"colspan="2"><s:property value="baseTable.resMsg"/></td>
</tr>
<tr>
  <td><SPACER height="5" type="block"></td>
</tr>
<tr>
  <td colspan="2" align="center" style="white-space: nowrap">
  <s:submit action="SaveCustomTable" id="saveCustomTable" value="Save" onclick="return false;" theme="simple"/>
    <s:submit action="ReloadCustomTable" id="reloadCustomTable" value="Reload" onclick="return false;" theme="simple"/>
  </td>
</tr>
<script type="text/javascript">
//submit the records
$('#saveCustomTable').click(function() {
  $("#tdResMsg").val("Saving records. Please wait.")
  $("#${baseTable.ajaxTarget}").load('../${namespace}/SaveCustomTable.action?btName=${baseTable.tableName}&btRecID=${baseTable.recordID}&rtSize=${baseTable.lstRecord.size()}&target=${baseTable.ajaxTarget}', $("#saveRecord").serialize());
});
//reload the records
$('#reloadCustomTable').click(function() {
  $("#tdResMsg").val("Reloading records. Please wait.")
  $("#${baseTable.ajaxTarget}").load('../${namespace}/ReloadCustomTable.action?btName=${baseTable.tableName}&btRecID=${baseTable.recordID}&rtSize=${baseTable.lstRecord.size()}&target=${baseTable.ajaxTarget}', $("#saveRecord").serialize());
});
</script>


This is a very brief explanation of the process I use. Please comment if you would like some additional information or have any questions.

Sunday, February 5, 2012

MyBatis – Database Change Logging

Here's how I handle recording database record changes. Changes are logged to a database table with the old values, new values, and ID of the user that made the change.

I set up an abstract Java class, StandardTable, that is used to base the classes for the main database tables:

/**
 * 
 * @author rgolebiowski
 *All of the main table classes are extended from this class.
 *All of the main database tables use ID field for the primary key
 *
 */
public abstract class StandardTable {
    private Integer ID;
    
    public Integer getID() {
        return ID;
    }

    public void setID(Integer ID) {
        this.ID = ID;
    } 
}


All of the Java classes for the main tables are extended from StandardTable. Example:

/**
 * 
 * @author rgolebiowski
 *Model for the AppTable table
 *
 */
public class AppTable extends StandardTable implements Serializable{


I set up an abstract Java class, TableRecord, to encapsulate standard methods for reading and saving StandardTable records:

/**
 * Abstract class for reading in and saving (insert or update) a record
 * @author rgolebiowski
 *
 */
public abstract class TableRecord {
 protected StandardTable tblRecord;//record being viewed or edited
 
 public abstract void loadRecord(Integer id);
 public abstract void saveRecord(AppUser appUser);
 
 public Object getTblRecord() {
  return tblRecord;
 }

 public void setTblRecord(Object tblRecord) {
  this.tblRecord = (StandardTable) tblRecord;
 }
}


All of the Java classes for viewing and editing records from the main tables are extended from TableRecord . Example:

/**
 * TableRecord for the AppTable database
 * @author rgolebiowski
 *
 */
public class AppTableRecord extends TableRecord {
 
 public AppTableRecord(){  
 }
 
 public AppTableRecord(AppTable tblRecord){
  this.setTblRecord(tblRecord);
 }

 /**
  * @param id: Record ID for the record to be loaded
  */
 public void loadRecord(Integer id) {
  GenericDataService genService = new GenericDataService("AppTableMapper");
  this.setTblRecord(genService.getByID(id));
 }

 /**
  * @param appUser: User that is updating the table
  */
 public void saveRecord(AppUser appUser) {
   try {
     //Switch MyBatis to map to the AppTAble database
     GenericDataService genService = new GenericDataService("AppTableMapper");
     //get the current record
     Object current = genService.getByID(((AppTable) this.tblRecord).getID());
     //log changes
     LogChange.log(appUser.getUserName(), current, this.tblRecord); 
     //if this is a current record then update
     if(((AppTable) this.getTblRecord()).getID() != null){
       genService.updateByID(this.getTblRecord());
     }
     else{
       //do an insert an set the record ID with the returned record ID
       this.setTblRecord(genService.insert(this.getTblRecord()));
     }
   } catch (IntrospectionException e) {
    e.printStackTrace();
   } catch (IllegalAccessException e) {
    e.printStackTrace();
   } catch (InvocationTargetException e) {
    e.printStackTrace();
   }
 }
}


Class for logging the database changes:

/**
 * Logger for databases changes
 * @author rgolebiowski
 *
 */
public class LogChange {

 /**
  * Constructs the string used to log the database changes
  * @param userID: UserID of the user making the change
  * @param bOld: Old object
  * @param bNew: New object
  * @throws IntrospectionException
  * @throws IllegalAccessException
  * @throws InvocationTargetException
  */
 public static void log(String userID, Object bOld, Object bNew) throws IntrospectionException, IllegalAccessException, InvocationTargetException {
    String res = "";//String to hold the change record
    boolean changed = false;
    try {
   if(bOld != null){ //if this is an update
       BeanInfo beanInfo = Introspector.getBeanInfo(bOld.getClass());
       res = bOld.getClass().getSimpleName() + " - ";
       //loop and compare old values with new values and add them to our string if they are changed
       for (PropertyDescriptor prop : beanInfo.getPropertyDescriptors()) {
           Method getter = prop.getReadMethod();
           Object vOld = getter.invoke(bOld); //old value
           Object vNew = getter.invoke(bNew); //new value
           if (vOld == vNew || (vOld != null && vOld.equals(vNew))) {
             continue;
           }
           changed = true;
           res = res + "(" + prop.getName()  + ", " +  vOld  + ", " + vNew + ")";
       }
     }
     else{//this is a new record
           changed = true;     
    BeanInfo beanInfo = Introspector.getBeanInfo(bNew.getClass());
    res = bNew.getClass().getSimpleName() + " - "; 
    //loop and create the string to log the new record
    for (PropertyDescriptor prop : beanInfo.getPropertyDescriptors()) {
      if(prop.getName().equals("class")) continue;       
      Method getter = prop.getReadMethod();
      Object vNew = getter.invoke(bNew);
      if (vNew != null) {
         res = res + "(" + prop.getName()  + ", " + vNew + ")";
      }
    }  
     }
     if (changed){
        logToDB(userID, res);
     }
  }
  catch (IllegalArgumentException e) {
    e.printStackTrace();
  }
  }
   
   /**
    * Saves the record to the database
    * @param userID: UserID of the user making the change
    * @param message: This string contains the changes
    */
   private static void logToDB(String userID, String message){
  ChangeLog changeLog = new ChangeLog();
  changeLog.setUserID(userID);
  changeLog.setMessage(message);
  SqlSessionFactory sqlSessionFactory = MyBatisConnectionFactory_INform.getSqlSessionFactory();
  SqlSession session = sqlSessionFactory.openSession();
  try {
      session.insert("ChangeLogMapper.insert", changeLog);
      session.commit();
  } finally {
      session.close();
  } 
   }
}


Then in my Strusts action I just have a simple method that calls saveRecord for my class to save the record to the database and record the changes. I just save it and forget about it!

    /**
     * Used to save the record to the database
     * @return SUCCESS
     * @throws Exception
     */
    public String saveRecord() throws Exception {
     this.tableRecord.saveRecord((AppUser) SessionService.getAttributeFromSession(request, SessionService.AppUser));
     this.resMsg = "Record saved.";
     return SUCCESS;
    }


The SQL script to create the ChangeLog database:
CREATE TABLE [dbo].[ChangeLog](
 [ID] [int] IDENTITY(1,1) NOT NULL,
 [Date] [datetime] NULL,
 [UserID] [varchar](50) NULL,
 [Message] [varchar](max) NULL,
 CONSTRAINT [PK_ChangeLog] PRIMARY KEY CLUSTERED 
(
 [ID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

SET ANSI_PADDING OFF
GO

ALTER TABLE [dbo].[ChangeLog] ADD  CONSTRAINT [DF_ChangeLog_Date]  DEFAULT (getdate()) FOR [Date]
GO



Saturday, January 28, 2012

Struts 2 - Dynamic Map Key

To get a value from a map that is in our action class declared as:
    private HashMap<String, String> Values;
with a getter decalred as:
    public String getValue(String key) {
        return Values.get(key);
    }   
with a key declared as:
    private String keyName;

In the JSP, call the getter like this:
    <s:property value="%{getValue(keyName)}"/>