No words wasted! Getting to the point about the work I do, the problems I deal with, and some links to posts about where I work.
Translate
Showing posts with label Java. Show all posts
Showing posts with label Java. Show all posts
Friday, October 15, 2021
Tomcat - Unable to locate persistence units error
I recently was working on updating the libraries for a Java web application and was unable to connect to the database. Stepping through the program in Tomcat I saw that it was giving me the "Unable to locate persistence units" error. This puzzled me for a long time, so I decided to take a closer look as I stepped through the application. The actual error message in the exception was "unrecognized persistence.xml version 2.2"! So I reverted back to version 2.1!
Thursday, October 8, 2015
Java - Search for AD Groups and List Group Attributes
Added the ability to search for AD groups and get a Group's Attributes to my LDAP class:
/**
*
* @param groupName The name of the AD group.
* @return The AD attributes for the group or null if error.
* @throws NamingException
*/
public Attributes getADGroupAttributes(String groupName) throws NamingException {
this.userName = null;
String searchFilter = "(&(objectClass=group)(cn=" + groupName + "))";
SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
NamingEnumeration results = ctx.search("dc=MYDOMAIN,dc=LOCAL", searchFilter, searchControls);
SearchResult searchResult = null;
if(results.hasMoreElements()) {
searchResult = (SearchResult) results.nextElement();
//make sure there is not another item available, there should be only 1 match
if(results.hasMoreElements()) {
this.strRes = "Matched multiple groups for the group name: " + groupName;
return null;
}
}
else{
this.strRes = "No groups found";
return null;
}
return searchResult.getAttributes();
}
/**
*
* @param groupName The group name to search for. Can use wild cards.
* @return A comma delimited list of the AD group names
* @throws NamingException
*/
public String searchforADGroup(String groupName) throws NamingException {
this.userName = null;
String searchFilter = "(&(objectClass=group)(cn=" + groupName + "))";
SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
NamingEnumeration results = ctx.search("dc=MYDOMAIN,dc=LOCAL", searchFilter, searchControls);
SearchResult searchResult = null;
String strGroups = "";
if(results!= null) {
try {
while(results.hasMore()) {
searchResult = (SearchResult) results.nextElement();
strGroups = strGroups + searchResult.getAttributes().get("cn") + ",";
}
} catch (Exception e) {
}
}
else{
this.strRes = "No groups found";
return null;
}
return strGroups;
}
Tuesday, October 6, 2015
Java - Secure LDAP - simple bind failed: internews.local:636 Error
This took me a while to fix because I had to get the right certificates to install in the Java certificate store on the new server. I finally found the "cer" files on one of the domain controllers, copied the files to the new server, and then used the Java keytool utility to import the certificates into the Java certificate store.
Some tips:
Use "keytool.exe" located the the Java bin folder to import the certificates.
Import the certificates into the "cacerts" file located in the security folder under "jre\lib\security".
Some tips:
Use "keytool.exe" located the the Java bin folder to import the certificates.
Import the certificates into the "cacerts" file located in the security folder under "jre\lib\security".
Wednesday, September 9, 2015
Spring - Using @Async to execute methods asynchronously
I have a long running database update that was added to a web application. Didn't seem right to have a method running for so long with the final result being a "Success!" message on the web page and no feedback to the client in the meantime so I made the method run asynchronously using Spring @EnableAsync and @Async. You can get my Eclipse test web application at Git Project.
Saturday, August 29, 2015
Java - Authenicate against Active Directory
It turns out it's very easy using JNDI to authenticate someone against Active Directory.
/**
* Authenticate against AD
* @param username The user name
* @param password The password
* @return True if authenticate
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public boolean authenicate(String username, String password){
boolean bRes = true;
ResourceBundle rsBun = ResourceBundle.getBundle("LDAP_Res");
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
//Use the secure connection
env.put(Context.PROVIDER_URL, rsBun.getString("urls"));
//Add the domain name if the user name does not contain it
if (username.indexOf(rsBun.getString("domain")) == -1){
username = rsBun.getString("domain") + "\\" + username;
}
env.put(Context.SECURITY_PRINCIPAL, username);
env.put(Context.SECURITY_CREDENTIALS, password);
try {
LdapContext ctx = new InitialLdapContext(env,null);
ctx.close();
} catch (NamingException e) {
bRes = false;
}
return bRes;
}
Saturday, August 15, 2015
Java - Using web socket for push notifications
I was working on a small web application that had a long running server process and I thought it would be good to send a progress notification back to the user. The client is identified on the server by their session.
The controller:
The session configurator:
The JavaScript:
In any server process when we want to send a message to the web client we call:
The controller:
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Iterator;
import java.util.Map.Entry;
import javax.servlet.http.HttpSession;
import javax.websocket.EndpointConfig;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
@ServerEndpoint(value = "/mainwebsock",
configurator = GetHttpSessionConfigurator.class)
public class MainControllerWebSocket {
private static Map mSessions = new HashMap();
@OnMessage
public void onMessage(String message, Session session) throws IOException,
InterruptedException {
System.out.println("User input: " + message);
session.getBasicRemote().sendText("Hello world Mr. " + message);
// Sending message to client each 1 second
for (int i = 0; i <= 6; i++) {
session.getBasicRemote().sendText(i + " Message from server");
Thread.sleep(1000);
}
}
public static void sendMessage(String message, String strSessionID){
try {
@SuppressWarnings("resource")
Session mySes = mSessions.get(strSessionID);
if (mySes != null && mySes.isOpen()) mySes.getBasicRemote().sendText(message);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@OnOpen
public void open(Session session, EndpointConfig config) {
HttpSession httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName());
mSessions.put(httpSession.getId(), session);
System.out.println("Client connected on session: " + httpSession.getId());
}
@SuppressWarnings("rawtypes")
@OnClose
public void onClose(Session session) {
for (Iterator iter = mSessions.entrySet().iterator(); iter.hasNext();) {
@SuppressWarnings("unchecked")
Map.Entry e = (Entry) iter.next();
if (e.getValue().getId() == session.getId() ) {
iter.remove();
}}
System.out.println("Connection closed");
}
}
The session configurator:
import javax.servlet.http.HttpSession;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
public class GetHttpSessionConfigurator extends ServerEndpointConfig.Configurator
{
@Override
public void modifyHandshake(ServerEndpointConfig config,
HandshakeRequest request,
HandshakeResponse response)
{
HttpSession httpSession = (HttpSession)request.getHttpSession();
config.getUserProperties().put(HttpSession.class.getName(),httpSession);
}
}
The JavaScript:
var webSocket = null;
function connect() {
var wsURI = 'ws://' + window.location.host + '/INagresso/mainwebsock';
console.log("Connecting");
webSocket = new WebSocket(wsURI);
webSocket.onerror = function(event) {
displayMessage(event.data);
};
webSocket.onopen = function(event) {
displayMessage("Connection open.");
};
webSocket.onmessage = function(event) {
displayMessage(event.data);
};
}
function displayMessage(data) {
document.getElementById('main_content').innerHTML += '
' + data;
}
function disconnect() {
if (webSocket !== null) {
webSocket.close();
webSocket = null;
}
displayMessage("WebSocket closed.");
}
//Button click handler
function createADAcccount(){
if (webSocket == null) {connect();}
$('div#main_content').empty();
$('li#value_input').html('<h1>Create AD Accounts</h1>');
$("div#main_content").load('${pageContext.request.contextPath}/testCreateAccountsAD');
}
In any server process when we want to send a message to the web client we call:
//strMessage is the message we want to send
//and mySession.getId() is the HttpSession Id
MainControllerWebSocket.sendMessage(strMessage, mySession.getId());
Git Project
Tuesday, September 9, 2014
Hibernate NamedQueries and NamedNativeQueries Errors
Working on Android client/server project I ran into some problems with Hibernate 4.3.1 annotations and Java code for Named Queries and Native Named Queries. The errors I was receiving were along the lines of "error in named query". Below is an example of the annotations and code that finally worked for me when querying an MSSQL database. Note the differences between the NamedQuery and corresponding NativeNamedQuery.
For a MySQL database the NamedQuery did not work.
The annotations:
The code to create the query:
For a MySQL database the NamedQuery did not work.
The annotations:
@Entity
@Table(name="Member")
@NamedQueries({
@NamedQuery(name="Member.loadByEmail", query="select m from Member m where email= :email"),
@NamedQuery(name="Member.loadByEmailAndPassword", query="select m from Member m where email= :email and password= :pwd")
})
@NamedNativeQueries({
@NamedNativeQuery(name="Member.nLoadByEmail", query="select m.* from Member m where email= :email", resultClass=Member.class),
@NamedNativeQuery(name="Member.nLoadByEmailAndPassword", query="select m.* from Member m where email= :email and password= :pwd", resultClass=Member.class)
})
public class Member implements Serializable{...
The code to create the query:
public Object findMemberByEmail(String email){
return em.createNamedQuery("Member.LoadByEmail", Member.class).setParameter("email", email).getSingleResult();
}
public Object nFindMemberByEmail(String email){
return em.createNamedQuery("Member.nLoadByEmail").setParameter("email", email).getSingleResult();
}
Friday, November 2, 2012
Java - Missing User When Querying Active Directory
In an application that I'm working on I'm using the "DirContext" to query Active Directory for users and permissions. A new user wasn't showing up in the query results. It turned out that since I was filtering my search on the City and he was set up without having an entry for City he wouldn't be returned in the search.
Wednesday, May 2, 2012
JNDI - Read Active Directory User Information
Today I needed to read in some user information from Active Directory so that I could create entries for each user and the uers group meberships in a SQL table.
Here is the code I used for testing:
All you need to do is provide your own connection settings in the connect method. I'm getting mine from a properties file.
This is my test method calling the methods in the class above:
Here is the code I used for testing:
package org.inewsnet.ldap;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.ResourceBundle;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
public class LdapJndi{
@SuppressWarnings("rawtypes")
Hashtable env;
DirContext ctx;
@SuppressWarnings({ "unchecked", "rawtypes" })
public boolean connect(){
boolean bRes = true;
ResourceBundle rsBun = ResourceBundle.getBundle("LDAP");
env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://" + rsBun.getString("server") + ":" +
rsBun.getString("port") + rsBun.getString("root"));
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, "cn=" + rsBun.getString("principal"));
env.put(Context.SECURITY_CREDENTIALS, rsBun.getString("credentials"));
try {
ctx = new InitialDirContext(env);
} catch (NamingException e) {
e.printStackTrace();
bRes = false;
}
return bRes;
}
public static String getCN(String cnName) {
if (cnName != null && cnName.toUpperCase().startsWith("CN=")) {
cnName = cnName.substring(3);
}
int position = cnName.indexOf(',');
if (position == -1) {
return cnName;
} else {
return cnName.substring(0, position);
}
}
public static String getUserName(String upn) {
int position = upn.indexOf('@');
return upn.substring(0, position);
}
@SuppressWarnings("rawtypes")
public boolean testNetworkRead() {
boolean bRes = false;
String firstName;
String lastName;
String userName;
ResourceBundle rsBun = ResourceBundle.getBundle("LDAP");
String bsaeOU = "ou=" + rsBun.getString("baseOU");
SearchControls sc = new SearchControls();
String[] attributeFilter = {"memberOf", "userPrincipalName", "sn", "givenName", "cn", "mail" };
sc.setReturningAttributes(attributeFilter);
sc.setSearchScope(SearchControls.SUBTREE_SCOPE);
String filter = "(&(sn=*)(l=*))";
try {
NamingEnumeration results = ctx.search(bsaeOU, filter, sc);
Integer iCx = 0;
while (results.hasMore()) {
SearchResult sr = (SearchResult) results.next();
Attributes attrs = sr.getAttributes();
iCx = iCx + 1;
System.out.print(iCx + ")");
Attribute attr = attrs.get("sn");
lastName = attr.get().toString();
System.out.print("ln= " + lastName);
attr = attrs.get("givenName");
if (attr != null) {
firstName = attr.get().toString();
System.out.print(" fn= " + firstName);
}
System.out.print("-");
Attribute aupn = attrs.get("userPrincipalName");
userName = getUserName(aupn.get().toString());
Attribute mattr = attrs.get("memberOf");
System.out.println(" un= " + userName + ": ");
if(mattr != null) {
//loop through the memberof attribute to get each group
for ( Enumeration e1 = mattr.getAll() ; e1.hasMoreElements() ; ) {
System.out.println(getCN(e1.nextElement().toString()));
}
}
}//end while
bRes = true;
} catch (Exception e) {
e.printStackTrace();
}
try {
ctx.close();
} catch (NamingException e) {
e.printStackTrace();
}
return bRes;
}
}
All you need to do is provide your own connection settings in the connect method. I'm getting mine from a properties file.
This is my test method calling the methods in the class above:
public String testLdapNetworkRead() throws Exception {
LdapJndi myCon = new LdapJndi();
try {
if(myCon.connect()) myCon.testNetworkRead();
} catch (Exception e) {
this.errorMsg = e.getMessage();
return ERROR;
}
successMsg = "Test Connect OK";
return SUCCESS;
}
Sunday, April 8, 2012
Java, XML - Read/Write a List of Objects
One of the things that I have been doing in my applications is using XML files to store lists. As illustrated by these two short methods, the XStream library makes this very easy to do .
Write a list of class FieldDescriptor to a file:
Reada list of class FieldDescriptor from a file:
Write a list of class FieldDescriptor to a file:
private static void saveFieldDefinitions(String fieldDescriptorName, LinkedList<FieldDescriptor> list) throws IOException {
XStream xs = new XStream();
xs.alias("FieldDescriptor", FieldDescriptor.class);
//Write
try {
FileOutputStream fs = new FileOutputStream(fieldDefinitionsFilePath + "\\" + fieldDescriptorName + ".xml");
xs.toXML(list, fs);
fs.close();
} catch (FileNotFoundException e1) {
e1.printStackTrace();
}
}
Reada list of class FieldDescriptor from a file:
protected void loadFieldDefinitions(String fieldDescriptorName) {
XStream xs = new XStream(new DomDriver());
xs.alias("FieldDescriptor", FieldDescriptor.class);
//Read
try {
FileInputStream fs = new FileInputStream(fieldDefinitionsFilePath + "\\" + fieldDescriptorName + ".xml");
lstFieldDescriptor = (List<FieldDescriptor>) xs.fromXML(fs);
fs.close();
} catch (FileNotFoundException e1) {
e1.printStackTrace();
} catch(Exception e2) {
e2.printStackTrace();
}
}
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.
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:
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.
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.
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.
This is a very brief explanation of the process I use. Please comment if you would like some additional information or have any questions.
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.
Wednesday, February 29, 2012
Java - NoClassDefFoundError
Usually when you get an error telling you that a class cannot be found it really means that the class cannot be found. In my case I received this error because I was trying to run a Tomcat app from within Eclipse and the project I was running is referencing another project:
The error message said:
I new that the class, TableRecord, is in the referenced project. To fix the problem I just had to add the refernced project to the DevLoader:
The error message said:
java.lang.NoClassDefFoundError: Lcom/dataSpace/dataRecord/TableRecord;
I new that the class, TableRecord, is in the referenced project. To fix the problem I just had to add the refernced project to the DevLoader:
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:
All of the Java classes for the main tables are extended from StandardTable. Example:
I set up an abstract Java class, TableRecord, to encapsulate standard methods for reading and saving StandardTable records:
All of the Java classes for viewing and editing records from the main tables are extended from TableRecord . Example:
Class for logging the database changes:
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!
The SQL script to create the ChangeLog database:
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)}"/>
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)}"/>
Subscribe to:
Posts (Atom)