Friday, November 7, 2008

EJB3 example of adding/removing elements from a ManyToMany relationship

Here's the use case: I have two entities in a many to many relationship, a User and a Referral. A User can have many referrals assigned to them and a Referral can have many Users assigned to it.

Database:
users (User Table)
id (primary key)
username
...

referral (Referral Table)
id (primary key)
name
...

Referral_User_Queue Table (join table)
referral_id (foreign key to the referral table, id column)
user_id (foreign key to the user table, id column)

EJB's

@Entity
@Table(name = "users")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Integer id;
...
@JoinTable(name = "referral_user_queue", joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")}, inverseJoinColumns = {@JoinColumn(name = "referral_id", referencedColumnName = "id")})
@ManyToMany
private Collection referralIdCollection;

...
getters and setters
}

@Entity
@Table(name = "referral")
public class Referral implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Integer id;

...

@ManyToMany(mappedBy = "referralIdCollection")
private List userIdCollection;

...
getters and setters
}


The User is the owning entity

I have stateless session beans that are facades to these entities (since netbeans generates them for me). The calls I make below are simply calls that end up as "merge" and "find" nothing more. So a call to userFacade.updateUser(User user) ends up being em.merge(user) where em is the injected EntityManager in the stateless session bean.

Here's the test method (I'm using a servlet as a test client for simplicity)

public class TestReferralServlet extends HttpServlet {

@EJB
private UserFacadeLocal userFacade;

@EJB
private ReferralFacadeLocal referralFacade;

protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
this.moveReferral(out);
.....
}

private void moveReferral(PrintWriter out) {
//locate the referral, I just happen to have one with the
//primary key of 31
Referral referral = referralFacade.find(new Integer(31));

User me = userFacade.findUser(1);
User advisor = userFacade.findUser(3);
User drs = userFacade.findUser(5);
User counslor = userFacade.findUser(6);

out.println("
1) users size=" + referral.getUserIdCollection().size());

//you have to make a copy of the collection or you will get a
//concurrent update exception when removing from within a loop.
List users = new ArrayList(referral.getUserIdCollection().size());
for(User u : referral.getUserIdCollection()) {
users.add(u);
}

//clean out any users and start with a fresh slate
for(User u : users) {
referral.getUserIdCollection().remove(u);
u.getReferralIdCollection().remove(referral);
userFacade.updateUser(u);
referralFacade.updateReferral(referral);
}

//grab fresh copy just to make sure everything is clean
referral = referralFacade.find(new Integer(31));
out.println("
2) users size=" + referral.getUserIdCollection().size());

//add all users to the referral
referral.getUserIdCollection().add(me);
me.getReferralIdCollection().add(referral);

referral.getUserIdCollection().add(drs);
drs.getReferralIdCollection().add(referral);

referral.getUserIdCollection().add(counslor);
counslor.getReferralIdCollection().add(referral);

referral.getUserIdCollection().add(advisor);
advisor.getReferralIdCollection().add(referral);

userFacade.updateUser(me);
userFacade.updateUser(drs);
userFacade.updateUser(counslor);
userFacade.updateUser(advisor);
referralFacade.updateReferral(referral);

//grab fresh copy to make sure everyone was really added
referral = referralFacade.find(new Integer(31));
out.println("
3) users size=" + referral.getUserIdCollection().size());

for(User u : referral.getUserIdCollection()) {
out.println("
" + u.getFirstname());
}

//now remove drs only
drs.getReferralIdCollection().remove(referral);
referral.getUserIdCollection().remove(drs);
userFacade.updateUser(drs);
referralFacade.updateReferral(referral);

//grab fresh copy to make sure only drs was removed
referral = referralFacade.find(new Integer(31));
out.println("
4) users size=" + referral.getUserIdCollection().size());

for(User u : referral.getUserIdCollection()) {
out.println("
" + u.getFirstname());
}

//voila...everything is right in the world and I didn't write any jdbc code
}

}


Just remember to update both ends of the relationship or the cache will think it still has it. That's why I'm calling userFacade.update and referralFacade.update

Wednesday, November 5, 2008

Authentication Phase Listener - JSF

Use Case: trying to secure every page (except for the login page). The user must be logged in. I am using application level authentication and not container managed.

Basically, the blogs I found mostly worked, but I would get a blank screen if there was an authentication failure. It ends up that you need to ensure that the navigation case in the faces-config.xml file has the
tag listed. Otherwise, you are always one page behind.

Here's the faces-config.xml entry:

<navigation-rule>
<navigation-case>
<from-outcome>authFailure</from-outcome>
<to-view-id>/index.jsp</to-view-id>
<redirect/>
</navigation-case>
</navigation-rule>


Then in your phase listener, if authentication fails, do this:

context.responseComplete();
context.getApplication().getNavigationHandler().handleNavigation(context, null, "authFailure");


The "authFailure" is the from-outcome in the faces-config.xml file

That's it...

Thursday, October 23, 2008

Netbeans 6.1 EAR Autodeploy with Glassfish

This is a hybrid of the post: http://www.nabble.com/In-NetBeans6.0.1-with-GlassFish-V2-the-web-application-cannot-auto-deploy.-td15853830.html

Basically, when I used eclipse (myeclipse), I could edit a jsp and it would automatically be updated when I refreshed the browser. It wasn't just jsp's, I could edit managed beans, helper classes, anything except the config files and they would magically appear deployed. Sometimes if I saved with an error in the class it would kill the app and I would have to fix the error and redeploy manually.

When I switched to Netbeans, I lost this nice feature. I was forced to "undeploy/redeploy" each time I made a small change. What a pain.

So I googled and came up with this hybrid solution:

1. Turn off deployment in Netbeans to glassfish: Tools -> Servers, then chose GlassFish, then Options. Uncheck "Directory Deployment Enabled"

2. Edit the project's build.xml file and add a target to copy a newly created ear file to glassfish's autodeploy directory:


<target name="post-dist">
<echo>post dist copy ear</echo>
<copy file="dist/[project_name].ear"
todir="/Applications/NetBeans/glassfish-v2/domains/domain1/autodeploy" />
</target>


That's it, now when I chose "Clean and Build" all changes are autodeployed. I know that you can make this target a bit more generic, but I'll leave that to you.

Enjoy!

Wednesday, May 28, 2008

MAC 0SX with Netbeans 6.0 and Java 1.6

If you install the new 1.6 jdk, netbeans pretty much craps on you. Oh, it will run just fine, but try to create something like, let's say, a java class. It will let you name it, give it a package, then ....nothing. But hey, who needs to create a java class anyway?

http://www.netbeans.org/issues/show_bug.cgi?id=129227 describes the bug.

I didn't mess with moving things around, I simply did this:

Alternative (non-destructive) workaround.

Edit the file /Applications/NetBeans/NetBeans\ 6.1\ Beta.app/Contents/Resources/NetBeans/etc/netbeans.conf

add "-J-Djava.ext.dirs=/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home/lib/ext/" to the definition of "netbeans_default_options".

and that's it, restart netbeans and it works. mofo's

Here's the stack trace produced before the fix:

java.lang.UnsupportedClassVersionError: Bad version number in .class file
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:675)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:124)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:260)
at java.net.URLClassLoader.access$100(URLClassLoader.java:56)
at java.net.URLClassLoader$1.run(URLClassLoader.java:195)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
at java.lang.ClassLoader.loadClass(ClassLoader.java:316)
at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:280)
at java.lang.ClassLoader.loadClass(ClassLoader.java:251)
at org.netbeans.ProxyClassLoader.loadClass(ProxyClassLoader.java:213)
at java.lang.ClassLoader.loadClass(ClassLoader.java:251)
at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:374)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:242)
at sun.misc.Service$LazyIterator.next(Service.java:271)
at javax.script.ScriptEngineManager.initEngines(ScriptEngineManager.java:127)
at javax.script.ScriptEngineManager.access$000(ScriptEngineManager.java:55)
at javax.script.ScriptEngineManager$1.run(ScriptEngineManager.java:98)
at java.security.AccessController.doPrivileged(Native Method)
at javax.script.ScriptEngineManager.init(ScriptEngineManager.java:96)
at javax.script.ScriptEngineManager.(ScriptEngineManager.java:69)
at org.netbeans.modules.templates.ScriptingCreateFromTemplateHandler.engine(ScriptingCreateFromTemplateHandler.java:125)
at org.netbeans.modules.templates.ScriptingCreateFromTemplateHandler.accept(ScriptingCreateFromTemplateHandler.java:67)
at org.openide.loaders.MultiDataObject.handleCreateFromTemplate(MultiDataObject.java:694)
at org.openide.loaders.DefaultDataObject.handleCreateFromTemplate(DefaultDataObject.java:159)
at org.openide.loaders.DataObject$CreateAction.run(DataObject.java:1247)
at org.openide.loaders.DataObjectPool$1WrapAtomicAction.run(DataObjectPool.java:238)
at org.openide.filesystems.EventControl.runAtomicAction(EventControl.java:120)
at org.openide.filesystems.FileSystem.runAtomicAction(FileSystem.java:499)
at org.openide.loaders.DataObjectPool.runAtomicAction(DataObjectPool.java:250)
at org.openide.loaders.DataObject.invokeAtomicAction(DataObject.java:861)
at org.openide.loaders.DataObject.createFromTemplate(DataObject.java:793)
at org.openide.loaders.DataObject.createFromTemplate(DataObject.java:773)
at org.netbeans.modules.java.j2seproject.J2SEProjectGenerator.createMainClass(J2SEProjectGenerator.java:368)
at org.netbeans.modules.java.j2seproject.J2SEProjectGenerator.access$200(J2SEProjectGenerator.java:78)
at org.netbeans.modules.java.j2seproject.J2SEProjectGenerator$1.run(J2SEProjectGenerator.java:116)
at org.openide.filesystems.EventControl.runAtomicAction(EventControl.java:120)
at org.openide.filesystems.FileSystem.runAtomicAction(FileSystem.java:499)
at org.netbeans.modules.java.j2seproject.J2SEProjectGenerator.createProject(J2SEProjectGenerator.java:97)
at
org.netbeans.modules.java.j2seproject.ui.wizards.NewJ2SEProjectWizardIterator.instantiate(NewJ2SEProjectWizardIterator.java:181)
at org.openide.loaders.TemplateWizard$InstantiatingIteratorBridge.instantiate(TemplateWizard.java:1023)
at org.openide.loaders.TemplateWizard.handleInstantiate(TemplateWizard.java:595)
at org.openide.loaders.TemplateWizard.instantiateNewObjects(TemplateWizard.java:416)
at org.openide.loaders.TemplateWizardIterImpl.instantiate(TemplateWizardIterImpl.java:253)
at org.openide.loaders.TemplateWizardIteratorWrapper.instantiate(TemplateWizardIteratorWrapper.java:165)
at org.openide.WizardDescriptor.callInstantiateOpen(WizardDescriptor.java:1384)
at org.openide.WizardDescriptor.callInstantiate(WizardDescriptor.java:1341)
at org.openide.WizardDescriptor.access$1600(WizardDescriptor.java:119)
at org.openide.WizardDescriptor$Listener$2$1.run(WizardDescriptor.java:1908)
at org.openide.util.RequestProcessor$Task.run(RequestProcessor.java:561)
[catch] at org.openide.util.RequestProcessor$Processor.run(RequestProcessor.java:986)

I get this exception trying to report the exception within the IDE. Looks like the URL is invalid.

java.io.IOException: Server returned HTTP response code: 403 for URL:
http://testwww.netbeans.org/nonav/uigestures/error.html
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1170)
at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:367)
at javax.swing.JEditorPane.getStream(JEditorPane.java:689)
Caused: java.io.IOException: Server returned HTTP response code: 403 for URL:
http://testwww.netbeans.org/nonav/uigestures/error.html
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:494)
at sun.net.www.protocol.http.HttpURLConnection$6.run(HttpURLConnection.java:1223)
at java.security.AccessController.doPrivileged(Native Method)
at sun.net.www.protocol.http.HttpURLConnection.getChainedException(HttpURLConnection.java:1217)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:906)
at javax.swing.JEditorPane.getStream(JEditorPane.java:719)
at javax.swing.JEditorPane.setPage(JEditorPane.java:406)
at org.netbeans.modules.uihandler.Installer$SubmitInteractive.assignInternalURL(Installer.java:1289)
at org.netbeans.modules.uihandler.Installer$Submit.doShow(Installer.java:888)
at org.netbeans.modules.uihandler.Installer.doDisplaySummary(Installer.java:475)
at org.netbeans.modules.uihandler.Installer.displaySummary(Installer.java:410)
at org.netbeans.modules.uihandler.UIHandler.run(UIHandler.java:138)
at org.openide.util.RequestProcessor$Task.run(RequestProcessor.java:561)
[catch] at org.openide.util.RequestProcessor$Processor.run(RequestProcessor.java:986)

Running MySQL as Solaris 10 Service

I found some great examples, but there was always a problem and when I enabled the services it would be in "maintenance" mode. blah.

Anyway, here are the steps that I followed:

1. create a manifest file in /var/svc/manifest/network called mysql.xml The manifest file is what solaris looks at start start/stop/etc on services. In it, you define the methods for starting, stopping, etc.

My manifest file looks like this:

service_bundle type='manifest' name='mysql:mysql'>

service
name='network/mysql'
type='service'
version='1'>
create_default_instance enabled='false' />
single_instance />


dependency name='fs'
grouping='require_all'
restart_on='none'
type='service'>
service_fmri value='svc:/system/filesystem/local' />


dependency name='net'
grouping='require_all'
restart_on='none'
type='service'>
service_fmri value='svc:/network/loopback' />


exec_method
type='method'
name='start'
exec='/lib/svc/method/svc-mysql start'
timeout_seconds='-1'>
method_context working_directory='/apps/mysql'>
method_credential user='mysql' group='mysql' />



exec_method
type='method'
name='stop'
exec=':kill'
timeout_seconds='-1'>


exec_method
type='method'
name='restart'
exec='/lib/svc/method/svc-mysql restart'
timeout_seconds='-1'>







make sure root:sys owns this file and that the permissions are 444
chown root:sys mysql.xml
chmod 444 mysql.xml

The main difference between this script and the other samples is when I would start, I got a "svc.startd could not set context for method: chdir: No such file or directory" error. Apparently, when you start the service under the mysql user/group, it looks for a working directory. On Solaris 10, the mysql user/group already exist with no home dir, so I added the "working_directory='/apps/mysql'" to the start method. This solved everything

2. Next, create the method file (script) which looks remarkably like something you'd put in the rc3.d dir. Here's what mine looks like (modified for my dirs)

#!/usr/bin/sh
#
# William Pool (Puddle) 01/05
# SMF Method file for MySQL
# E-mail: puddle@flipmotion.com
#
# This uses Sun's default MySQL packages
# SUNWmysqlu SUNWmysqlr
#
# Modify accordingly!
#
# NOTE: Make sure DB_DIR is owned BY the mysql user and group and chmod
# 700.
#

#.. /lib/svc/share/smf_include.sh
. /lib/svc/share/smf_include.sh

DB_DIR=/apps/mysql
PIDFILE=${DB_DIR}/`/usr/bin/hostname`.pid

case "$1" in
start)
/usr/sbin/mysqld_safe --user=mysql --datadir=${DB_DIR} --pid-file=${PIDFILE} > /dev/null &
;;
stop)
if [ -f ${PIDFILE} ]; then
/usr/bin/pkill mysqld_safe >/dev/null 2>&1
/usr/bin/kill `cat ${PIDFILE}` > /dev/null 2>&1 && echo -n ' mysqld'
fi
;;
'restart')
stop
while pgrep mysqld > /dev/null
do
sleep 1
done
start
;;
*)
echo ""
echo "Usage: `basename $0` { start | stop | restart }"
echo ""
exit 64
;;
esac

#---EOF

make sure root:bin owns it and the permissions are 555
chown root:bin svc-mysql
chmod 555 svc-mysql

make sure the data dir is owned by mysql user/group
chown -R mysql:mysql

3. now verify that the manifest file is good
svccfg validate /var/svc/manifest/network/mysql.xml

4. import the manifest file
svccf import /var/svc/manifest/network/mysql.xml

5. check the status
svcs mysql

The status should be "disabled", so enable it
svcadm enable mysql

6. check the status again
svcs mysql

It should be "online". If it is "maintenance", then there was a problem starting it. Look in /var/svc/log/network-mysql..... log file and look for errors. You can also do a svcs -x mysql command it will be tell you some info. The log file is where I found the error about the working directory. Of course the error wasn't that nice, but whatever.

Now you have mysql running as a service on Solaris 10. yeah.

Monday, April 21, 2008

Commons-el.jar and glassfish

I know, been a long time. If you place the commons_el.jar file from the jakarta project anywhere in the glassfish lib dirs (glassfish/lib glassfish/domain1/domain/lib) glassfish will start just fine, but weird things happen in the web gui. When you try to deploy an ear,war,jar you will get a:

javax.servlet.ServletException: String index out of range: -1

root cause

java.lang.StringIndexOutOfBoundsException: String index out of range: -1


This doesn't always happen, as sometimes it will work and you get to the deploy page.
Other times, you can be misled like it is deploying, then you get the error.
To avoid this, you will need to bundle the commons_el.jar with the web app and not
make it global. At least that is the only way I've found so far...

Tuesday, February 19, 2008

MySQL 5 foreign keys

Perhaps it was just my ignorance on all things mysql, but if you create an MyISAM table, there are no real foreign keys. It actually just creates an index when you try to create one.

If you want actual foreign keys (that reverse engineering will pick up), then change the engine to InnoDB.

This link talk about how to do this: http://dev.mysql.com/doc/refman/5.0/en/converting-tables-to-innodb.html

The mysql query browser tool will also do it for you, click on "Table Options" when editing the table. If you already have a bunch of data in the table, see the link above.

Sunday, January 27, 2008

OSX Leopard + Glassfish Read This

Ok, after many frustrating hours/days, I finally found a thread that fixed this problem:

when starting glassfish, it fails when it attempts to initialize the AMX and JMX stuff. You get a connection refused because the jmx stuff is trying to connect to 169.254.x.x

I guess leopard uses this for it's wireless stuff now (the ip) and no amount of altering of network settings will work. I even tried routing all traffic to localhost from 169... No dice.

The fix: in the /domains/domain1/config/domain.xml file, edit the

jmx-connector accept-all="false" address="localhost" auth-realm-name="admin-realm" enabled="true" name="system" port="8686" protocol="rmi_jrmp" security-enabled="false"

line. By default, the enabled="true" is set. change this to enabled="false" You lose the ability to administer glassfish remotely, but at least it will start.

I'm sure glassfish will work on a patch, eventually.

Friday, January 25, 2008

EJB3 - Temporal Types

With EJB3 (and jpa, etc) dates can be a little confusing. If you need just the date (no time), use @Temporal(TemporalType.DATE) and you will only get thet date. The time will be zeroed out.

If you want the date with the time, use @TemporalType(TemporalType.TIMESTAMP) and you will get both the date and time.

In both cases, still use java.util.Date or java.util.Calendar (I use java.util.Date). With the eclipse ide, when reverse engineering tables to entities, by default it will plug in java.util.Date with the TemporalType.DATE and you lose the time.

Anyway, hope this helps!