Monday, July 7, 2008

Flex BlazeDS XML Parsing

I've received some great comments on my recent entry Simplified BlazeDS and JMS. A few people had some issues, and I wanted to clarify one thing I discovered early on, fixed, forgot, and then was reminded of as others encountered the same problem -- Flex BlazeDS xml parsing does not remove any spaces or linefeeds from the xml between the xml tags and the value. If, for instance, your xml formatter places line feeds between your xml tags and the containing text values, you will encounter problems.

Also I've set up an email at mmartinsoftware@comcast.net for those who wish to email me or want sample code emailed to them. I'll have to look for some hosting space to make it easier down the road.

Friday, May 16, 2008

OpenLDAP 2.4.9 Multi-Master Replication Stability Improved

I pulled in OpenLDAP 2.4.9 to re-visit some multi-master testing I had done on the previous version. 2.4.9 was a better experience for me than 2.4.8. I re-ran some tests of running two replicated servers on the same machine. I would continuously load in several hundred or thousand user records on one server, then delete them.

The stability is improved from 2.4.8. I have yet to have a server core on me--even after abruptly shutting done one or both servers during a batch load or delete.

My only issue now is that when performing a bulk load, if you interrupt the client during the load ( Control-c ldapadd ) the servers get out of sync. The server you directly attach to in order to load the records contains more records than the "replicated" server. Under most circumstances, this would not be a huge issue, but it does show an easy way for a client to get the servers out of sync.

With the 2.4.9 release of OpenLDAP, I would trust the multi-master synchronization enough now for use on production systems.

Thursday, May 8, 2008

Simplified BlazeDS and JMS

I've been using Flex for some time now, and finally got around to trying out BlazeDS. What really peaked my interest was it's claim to publish and consume JMS messages from both topics and queues.

The one area in web development that I always struggle with is asynchronous messaging from the server to clients over HTTP. I'm pleased to announce that I got a simple application up and running.

The goal of this post is to share the example and lessons learned in order to save others some of the problems I ran into.

My project had a few main goals:
  • Get a simple pub/sub example up and running
  • Avoid using the "pre-configured" download. I wanted to get a project up and running "from scratch"
  • Use my existing ActiveMQ broker running in it's own JVM
  • Use my existing Tomcat install
  • Use mxml as much as possible and avoid writing Actionscript
  • Keep it simple to get a working example


Before I get into the example code, a few things I ran into:
  • The BlazeDS documentation is good, but far from perfect. The messaging examples switch from mxml to actionscript, so it makes it a little difficult to piece things together. Also, it's not clear in the examples what many of the values point to--i.e. is that the JMS Topic name or the destination name???? I also found a few syntax problems in the examples which caused some "cut and paste" problems for me.
  • A key piece I discovered after nothing was working at all, was the use of the "-services" flag to the mxmlc compiler. You have to compile your mxml file with the services file you plan to call.
  • Don't rely on client side faults. Prior to finding out about the "-services" flag, I would call the send method in the client, but nothing happened--the faultHandler was never called on the client, and nothing appeared in the server logs. Once I compiled with the "-services" flag, things began to click.
  • Make sure the JNDI names are correct. BlazeDS looks up the JMS ConnectionFactory and Topics via JNDI calls to Tomcat's JNDI. Make sure you specify the full JNDI name of the resource. i.e java:comp/env/jms/messageTopic



The example contains 5 main files:
  1. A standard web.xml file with a BlazeDS servlet to handle the remote calls
  2. A Tomcat context.xml file to add the JNDI values for the required JMS resources
  3. A BlazeDS services-context.xml file to set up the services and channels
  4. A BlazeDS messaging-config.xml that stores the messaging service(s).
  5. A test.mxml file which is compiled into the Flex application.


The image to the right shows the file locations in the final WAR layout.

web.xml
The web.xml file is pretty basic. The main 2 things to point out are the HttpFlexSession listener and the MessageBrokerServlet. The servlet loads up you services, so it is passed the location of the services-config.xml file inside the WAR file.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<display-name>BlazeDS</display-name>
<description>BlazeDS Application</description>

<!-- Http Flex Session attribute and binding listener support -->
<listener>
<listener-class>flex.messaging.HttpFlexSession</listener-class>
</listener>

<!-- MessageBroker Servlet -->
<servlet>
<servlet-name>MessageBrokerServlet</servlet-name>
<display-name>MessageBrokerServlet</display-name>
<servlet-class>flex.messaging.MessageBrokerServlet</servlet-class>
<init-param>
<param-name>services.configuration.file</param-name>
<param-value>/WEB-INF/flex/services-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>MessageBrokerServlet</servlet-name>
<url-pattern>/messagebroker/*</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>


context.xml
The context.xml file is for Tomcat. You can read more about it in the Tomcat documentation. The key take away is the "name" attribute is the JNDI name that has to match the jndi name used in the messaging-config.xml file.

<?xml version="1.0" encoding="UTF-8"?>
<Context path="/BlazeTest">
<Resource name="jms/flex/TopicConnectionFactory"
type="org.apache.activemq.ActiveMQConnectionFactory"
description="JMS Connection Factory"
factory="org.apache.activemq.jndi.JNDIReferenceFactory"
brokerURL="tcp://localhost:61616"
brokerName="myBroker"/>
<Resource name="jms/messageTopic"
type="org.apache.activemq.command.ActiveMQTopic"
description="a simple topic"
factory="org.apache.activemq.jndi.JNDIReferenceFactory"
physicalName="messageTopic"/>
</Context>


services-config.xml
The services-config.xml file is used by BlazeDS. It is first used when you go to compile the mxml file. When you deploy the war file to Tomcat, this file is also loaded up by the MessageBrokerServlet.

During compilation, you pass it in like this using the relative path to it from your mxml file.

~/java_libraries/flex3/bin/mxmlc -services=WEB-INF/flex/services-config.xml test.mxml

My current file turns on logging which can help during resolve messaging problems like jndi resources not being found. This file loads in the messaging-config.xml file which is described below.

The channels determine how the client will talk to the server. The endpoint has to match the servlet-mapping url-pattern contained in the web.xml. Since the servlet is grabbing /messagebroker/* and my application is deployed under BlazeTest, the endpoint becomes:

http://localhost:8080/BlazeTest/messagebroker/amf

The "amf" on the end of the URL can be called anything--it's what distinguishes one channel url from another channel url. Here, I am only using one channel.

<?xml version="1.0" encoding="UTF-8"?>
<services-config>
<services>
<service-include file-path="messaging-config.xml" />
</services>
<security/>
<channels>
<channel-definition id="my-amf" class="mx.messaging.channels.AMFChannel">
<endpoint url="http://localhost:8080/BlazeTest/messagebroker/amf" class="flex.messaging.endpoints.AMFEndpoint"/>
</channel-definition>
</channels>
<logging>
<target class="flex.messaging.log.ConsoleTarget" level="Debug">
<properties>
<prefix>[BlazeDS] </prefix>
<includeDate>false</includeDate>
<includeTime>false</includeTime>
<includeLevel>false</includeLevel>
<includeCategory>false</includeCategory>
</properties>
<filters>
<pattern>Endpoint.*</pattern>
<pattern>Service.*</pattern>
<pattern>Configuration</pattern>
</filters>
</target>
</logging>
</services-config>



messaging-config.xml
This file stores the JMS services and tells BlazeDS where to get the JMS ConnectionFactory and Topics from the JNDI lookup. The part here that caused be a little problem was making sure the connection-factory and destination-jndi-name values had the correct JNDI name Tomcat expected. Also, note the destination id value. When you set up the consumer or producer in the mxml file, you don't tell it what Topic or Queue to connect to, you tell it what destination to connect to. This makes sense, since the mx:Producer and mx:Consumer attribute is called "destination", but it did throw me for a little loop.

<?xml version="1.0" encoding="UTF-8"?>
<service id="message-service" class="flex.messaging.services.MessageService">
<adapters>
<adapter-definition id="actionscript" class="flex.messaging.services.messaging.adapters.ActionScriptAdapter" default="true" />
<adapter-definition id="jms" class="flex.messaging.services.messaging.adapters.JMSAdapter"/>
</adapters>
<default-channels>
<channel ref="my-amf"/>
</default-channels>
<destination id="message-destination">
<properties>
<jms>
<destination-type>Topic</destination-type>
<message-type>javax.jms.TextMessage</message-type>
<connection-factory>java:comp/env/jms/flex/TopicConnectionFactory</connection-factory>
<destination-jndi-name>java:comp/env/jms/messageTopic</destination-jndi-name>
<delivery-mode>NON_PERSISTENT</delivery-mode>
<message-priority>DEFAULT_PRIORITY</message-priority>
<acknowledge-mode>AUTO_ACKNOWLEDGE</acknowledge-mode>
<initial-context-environment>
<property>
<name>Context.INITIAL_CONTEXT_FACTORY</name>
<value>org.apache.activemq.jndi.ActiveMQInitialContextFactory</value>
</property>
<property>
<name>Context.PROVIDER_URL</name>
<value>tcp://localhost:61616</value>
</property>
</initial-context-environment>
</jms>
</properties>
<channels>
<channel ref="my-amf"/>
</channels>
<adapter ref="jms"/>
</destination>
</service>


test.mxml
The test.mxml is the final file. It's the GUI code. The file gets compiled into a swf file as I showed up above. My version uses mx:Producer and mx:Consumer mxml tags, though you can also instantiate these using Actionscript if you'd like. I prefer to write as little script as possible. The main key in this file was that I had to subscribe the consumer to it's destination before it would consume messages. Everything else is pretty simple, and you can see how easy it is to get a client sending and receiving messages to the server-side JMS broker over HTTP. This is why I like Flex!

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx='http://www.adobe.com/2006/mxml' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
backgroundGradientColors="[0xcfe4ff, 0xffffff]" paddingTop="5" paddingLeft="5"
paddingRight="5" paddingBottom="0" applicationComplete="logon()">
<mx:Script>
<![CDATA[
import mx.rpc.events.FaultEvent;
import mx.messaging.*;
import mx.messaging.messages.*;
import mx.messaging.events.*;
import mx.controls.Alert;

private function messageHandler(event:MessageEvent):void {
receiveTextArea.text = event.message.body + "\n" + receiveTextArea.text;
}
private function acknowledgeHandler(event:MessageAckEvent):void{
}
private function faultHandler(event:MessageFaultEvent):void {
Alert.show('Fault!', 'Alert Box', mx.controls.Alert.OK);
}


private function logon():void {
consumer.subscribe();
}

private function sendMessage():void {
var message:AsyncMessage = new AsyncMessage();
message.body = sendTextArea.text;
producer.send(message);
sendTextArea.text="";
}


]]>
</mx:Script>
<mx:Producer id="producer" destination="message-destination" acknowledge="acknowledgeHandler(event);" fault=" faultHandler(event)"/>
<mx:Consumer id="consumer" destination="message-destination" message="messageHandler(event)" acknowledge="acknowledgeHandler(event);" fault=" faultHandler(event)"/>
<mx:HBox>
<mx:TextInput id="sendTextArea"/>
<mx:Button label="Send" click="sendMessage();"/>
</mx:HBox>
<mx:TextArea id="receiveTextArea" width="50%" height="200"/>
</mx:Application>


The client app looks something like this:




In Conclusion, BlazeDS is an extremely simple way to use JMS with remote Flex clients over HTTP connections. The back-end configuation is somewhat complex, but mostly boilerplate and not too bad once you piece it together.

I haven't "stress" tested the messaging yet, or done very complex messaging to be able to say how robust and scalable it is, but so far, It does live up to it's claims. Best of all, I can now do asynchronous messaging to remote clients without writing code.

Wednesday, April 23, 2008

Java Self-Signed Certificates and Firefox 3 beta 5

A few weeks ago, I started having problems with Firefox 3 beta 5 and my self-signed certificates being used by some Tomcat servers. Though I could add the certificate as an exception, Firefox 3 beta 5 would not let me get in. I was using Java's keytool with the -genkeypair option to create the certificate.

I recently discovered a solution. keytool by default uses the DSA algorithm when generating the self-signed cert. Earlier versions of Firefox accepted these keys without problem. With Firefox 3 beta 5, using DSA doesn't work, but using RSA does. Passing "-keyalg RSA" when generating the self-signed certificate creates a cert the Firefox 3 beta 5 fully accepts.

I'm not sure if this is by Firefox design or not, but at least it's working for me.

Wednesday, February 27, 2008

More OpenLDAP

I tested OpenLDAP multimaster replication some more today. I went back to the official relase version 2.4.8. I ran my test on an AMD 64 bit machine with both servers on the same box--different ports and different installation folders. Things ran fine for the most part. Synchronization was consistent in loading and removing 5000 entries like this:


dn: cn=Fred XX_0,dc=mgm,dc=com
objectClass: inetOrgPerson
objectClass: top
givenName: Fred
sn: XX
cn: Fred XX_0


I don't know much about the berkeley db, but my issues seemed to happen after abrupt shutdowns and what I think are database corruptions. slapd cored on each server in the same place at different times during the testing:

Server 1 Core:

#0 is_ad_subtype (sub=0x0, super=0x832430) at ad.c:489
#1 0x00000000004213a7 in attrs_find (a=0x936998, desc=0x832430) at attr.c:647
#2 0x00000000004352bf in test_ava_filter (op=0x41801640, e=0x914a18, ava=0x41800fb0, type=163) at filterentry.c:617
#3 0x0000000000435761 in test_filter (op=0x41801640, e=0x914a18, f=0x41800fd0) at filterentry.c:88
#4 0x0000000000480d9b in bdb_search (op=0x41801640, rs=0x41800ec0) at search.c:845
#5 0x0000000000470be2 in overlay_op_walk (op=0x41801640, rs=0x41800ec0, which=op_search, oi=0x87b2b0, on=0x0) at backover.c:653
#6 0x00000000004710d5 in over_op_func (op=0x41801640, rs=0x41800ec0, which=op_search) at backover.c:705
#7 0x000000000046a56e in syncrepl_entry (si=0x8a31b0, op=0x41801640, entry=0x90add8, modlist=0x418015a8, syncstate=1,
syncUUID=, syncCSN=0x0) at syncrepl.c:1989
#8 0x000000000046c395 in do_syncrep2 (op=0x41801640, si=0x8a31b0) at syncrepl.c:844
#9 0x000000000046de8c in do_syncrepl (ctx=0x41801df0, arg=) at syncrepl.c:1226
#10 0x000000000041a692 in connection_read_thread (ctx=0x41801df0, argv=) at connection.c:1213
#11 0x00000000004fa6f4 in ldap_int_thread_pool_wrapper (xpool=0x83c890) at tpool.c:625
#12 0x00000035f1e06407 in start_thread () from /lib64/libpthread.so.0
#13 0x00000035f12d4b0d in clone () from /lib64/libc.so.6


After this happened, a restart of slapd would always hang on either server with this stack trace:


#0 0x00000035f1e076dd in pthread_join () from /lib64/libpthread.so.0
#1 0x00000000004e4501 in syncprov_db_open (be=0x8a27c0, cr=) at syncprov.c:2632
#2 0x0000000000470868 in over_db_func (be=0x8a27c0, cr=0x7fffad390610, which=) at backover.c:62
#3 0x0000000000427350 in backend_startup_one (be=0x8a27c0, cr=0x7fffad390610) at backend.c:224
#4 0x000000000042761a in backend_startup (be=0x8a27c0) at backend.c:316
#5 0x000000000040505a in main (argc=4, argv=0x7fffad3908d8) at main.c:932


I'm happy to see Gavin's interest in my blog. It shows his dedication--a tribute to the OpenSource community.

OpenLDAP MultiMaster Replication Redemption

After some tweaking of my slapd.conf, I was able to get multimaster replication working a lot more reliably than my earlier attempts. I pulled in the latest source code - whatever was comitted to cvs after 2.4.8 release, and tested with this version. Here is my current slapd.conf file syncrepl settings:


#serverID 1 ldap://server1:9009
serverID 2

overlay syncprov

syncRepl rid=1
provider=ldap://server1:9009
binddn="cn=Manager,dc=mgm,dc=com"
bindmethod=simple
credentials=ldap
searchbase="dc=mgm,dc=com"
type=refreshAndPersist
retry="5 + 5 +"
interval=00:00:00:05

syncRepl rid=2
provider=ldap://server2:9009
binddn="cn=Manager,dc=mgm,dc=com"
bindmethod=simple
credentials=ldap
searchbase="dc=mgm,dc=com"
type=refreshAndPersist
retry="5 + 5 +"
interval=00:00:00:05


mirrormode true
database monitor


It seems if you switch replication types from refreshAndPersist to refreshOnly, things get messed up. I prefer the refreshAndPersist. I left the interval option in my config, though it's not used in refreshAndPersist mode. Previously, my retry intervals were very short, so I think this is why I was getting un-predictable results in the number of entities replicated. The timeouts may have been hit and I wasn't waiting long enough.

I stress tested OpenLDAP much more than I have FDS. I would stress test by ctrl-c'ing a running slapd process while thousands of adds or deletes were being done to another server. Without hard stopping a server, replication seemed to work well. With hard stopping the server, the only issue I had was sometimes the stopped server would freeze and hang when coming back up and performing an ldapsearch at the same time--some sort of connection deadlock or something. I had to kill -9 slapd and start it again, then it would sync back up. Under most situations, servers being killed and started and killed and started again would not be a common occurrence.

I still saw a few SEGV's sometimes when bringing up a server after stopping it during bulk adds or deletes--like this one I just got. I deleted 5000 entries on server1, then as server2 was processing the deletes, I ctrl-c'd server2. Then bringing server2 back up. I got this:


#0 0x00ace375 in memmove () from /lib/libc.so.6
#1 0x0810a51c in bdb_dn2id_children (op=0x8d71028, txn=0x0, e=0x8be1cd4) at dn2id.c:351
#2 0x0810606f in bdb_cache_children (op=0x8d71028, txn=0x0, e=0x8be1cd4) at cache.c:1008
#3 0x080e4cac in bdb_hasSubordinates (op=0x8d71028, e=0x8be1cd4, hasSubordinates=0xa15caa0c) at operational.c:54
#4 0x080e4e09 in bdb_operational (op=0x8d71028, rs=0xa168c168) at operational.c:101
#5 0x080d3001 in overlay_op_walk (op=0x8d71028, rs=0xa168c168, which=op_aux_operational, oi=0x8b74868, on=0x8b74e38) at backover.c:653
#6 0x080d351d in over_op_func (op=0x8d71028, rs=0xa168c168, which=op_aux_operational) at backover.c:705
#7 0x0808066b in fe_aux_operational (op=0x8d71028, rs=0xa168c168) at backend.c:1868
#8 0x080802b9 in backend_operational (op=0x8d71028, rs=0xa168c168) at backend.c:1885
#9 0x08084531 in slap_send_search_entry (op=0x8d71028, rs=0xa168c168) at result.c:764
#10 0x080e7953 in bdb_search (op=0x8d71028, rs=0xa168c168) at search.c:869
#11 0x080d3001 in overlay_op_walk (op=0x8d71028, rs=0xa168c168, which=op_search, oi=0x8b74868, on=0x8b74e38) at backover.c:653
#12 0x080d351d in over_op_func (op=0x8d71028, rs=0xa168c168, which=op_search) at backover.c:705
#13 0x08076256 in fe_op_search (op=0x8d71028, rs=0xa168c168) at search.c:368
#14 0x08076a47 in do_search (op=0x8d71028, rs=0xa168c168) at search.c:217
#15 0x0807416c in connection_operation (ctx=0xa168c238, arg_v=0x8d71028) at connection.c:1084
#16 0x080748e0 in connection_read_thread (ctx=0xa168c238, argv=0x10) at connection.c:1211
#17 0x0816c9a4 in ldap_int_thread_pool_wrapper (xpool=0x8b4eea0) at tpool.c:663
#18 0x00d0650b in start_thread () from /lib/libpthread.so.0
#19 0x00b30b2e in clone () from /lib/libc.so.6



Running slapd again and it came up just fine and synchronized started again.

OpenLDAP multimaster replication is working a lot better than I first experienced. The multimaster replication is not bullet proof, but it's probably adequate now for many situations.

Sunday, February 24, 2008

OpenDS Looks Promising

Sun's OpenDS project -- https://opends.dev.java.net/ -- looks to be a very promising LDAP implementation. I haven't gotten into it much, but as I installed it this morning, I was pleasantly surprised.

The install was the easiest of FDS or OpenLDAP. A nice gui steps you through the initial install. Replication setup was simple, as the gui prompts you to identify another server already participating in the replication. OpenDS, by default, supports multi-master replication. I believe this is, in fact, the only replication it supports. I think it would be useful to have the ability to force read-only replicated servers, but I didn't see if this was possible.

I easily set up 3 servers on my machine ( a Dual-core Opteron 185 with 2GB of memory running Fedora 8 64bit ). Using OpenDS, I generated the example ldif of 10k users, and loaded it up. Replication started immediately. OpenDS provides a nice, simple gui for simple monitoring, so it was easy to see the updates going to the other 2 servers. participating in the replicated cluster.

The ldif additions were slow--it took several minutes to load the 10k users. My machine load went up past 7, and with running several servers, my computer was having to swap memory quite a bit. During the load, I shut down one server, brought it up for a minute or two, then down and up again. I wanted to see how this server would handle the synchronization when it was not up.

When the load finished, the replicated server that stayed up the entire time, had the same number of entries as the server I loaded the ldif into--10,003. The server I shut down, however, was about 50+ entries short with some errors in the replication log:

[24/Feb/2008:09:22:26 -0700] category=SYNC severity=MILD_ERROR msgID=14876739 msg=Could not replay operation AddOperation(connID=-1, opID=47, dn=uid=user.1333,ou=People,dc=example,dc=com) with ChangeNumber 000001184c39fe467f4300000537 error Canceled

Apart from that, OpenDS is off to an excellent start--especially for it's age. It's by far the easiest server to get up and running. I'll be watching as it matures to see how it performs and stabilizes.