Markdown text markup

Markdown looks like it might be nicer than textile…

And now, for some formating tests.

A Heading

Below is a blockquote (from DaringFireball):

Markdown is intended to be as easy-to-read and easy-to-write as is feasible.

Readability, however, is emphasized above all else. A Markdown-formatted document should be publishable as-is, as plain text, without looking like it’s been marked up with tags or formatting instructions. While Markdown’s syntax has been influenced by several existing text-to-HTML filters, the single biggest source of inspiration for Markdown’s syntax is the format of plain text email.

A Smaller Heading

Arbitrary HTML tags can also be used, such as span tags to set colours. Not that I’d do that of course.

  • This is an item
  • This is another item

A Really Small Heading

And these are:

  1. the first item
  2. the second item, and
  3. the third item

Planets provide OPML

This is probably not news to anyone else, but the ever-increasing world of blog “planets” (Planet Linux Australia, Kernel Planet, Debian Planet) often provide an OPML file containing all their subscriptions, ready for importing into your favourite aggregator (like NetNewsWire). Cool.

But how do I stay up to date when new feeds are added?

Setting up WordPress for a friend

Last night, I set up WordPress for a friend. They claim on the website it takes less than five minutes. And they were right! In fact, I also added a domain to my DNS, configured an apache vhost and set up an email forwarder in the same time. :-O

A subtle understatement...

From David A. Wheeler, in an article about the new US passports with “remote” scanning ability:

This is an area where the convenience of wireless is far outweighed by the disadvantages of getting murdered.

Er, yes. I can see how being murdered would be a down side.

A patch to walkah's image module

I just sent James the following patch to his new revamped drupal image module. It allows an admin to configure the title of the image modules “latest” and “random” image blocks.

Index: sandbox/walkah/image/image.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/sandbox/walkah/image/image.module,v
retrieving revision 1.16
diff -u -r1.16 image.module
--- sandbox/walkah/image/image.module   3 Mar 2005 15:44:48 -0000       1.16
+++ sandbox/walkah/image/image.module   4 Mar 2005 11:38:37 -0000
@@ -79,6 +79,11 @@
   $size_group .= form_checkbox(t('Allow users to view original image'), 
                           'image_view_original', 1, variable_get('image_view_original', 0));
   $output.= form_group(t('Image sizes'), $size_group);
 
+  // block titles
+  $titles .= form_textfield(t('Latest Image block title'), 
                   'image_block_title_latest', 
                   variable_get('image_block_title_latest', 'Latest image'), 30, 255);
+  $titles .= form_textfield(t('Random Image block title'), 
                   'image_block_title_random', 
                   variable_get('image_block_title_random', 
                   'Random image'), 30, 255);
+  $output.= form_group(t('Block Titles'), $titles);
+
   return $output;
 }
 
@@ -212,12 +217,12 @@
       switch($delta) {
         case 0:
           $images = image_get_latest();
-          $block['subject'] = t('Latest image');
+          $block['subject'] = variable_get('image_block_title_latest', 'Latest image');
           $block['content'] = l(image_display($images[0], 'thumbnail'), 'node/'.$images[0]->nid);
           break;
         case 1:
           $images = image_get_random();
-          $block['subject'] = t('Random image');
+          $block['subject'] = variable_get('image_block_title_random', 
                                          'Random image');
           $block['content'] = l(image_display($images[0], 'thumbnail'),
                                          'node/'.$images[0]->nid);
           break;
       }

An Ant task for sun app server ear verification

Here is an ant target that worked at least once to verify an EAR for deployment on SunONE…

<!--
    properties for SunONE app server deployment (can be overridden
    in local properties, otherwise use dev box by default)
-->
<property name="deploy.sunone.home" value="/usr/local/sunas8"/>
<property name="deploy.sunone.asadmin" value="${deploy.sunone.home}/bin/asadmin"/>

<property name="deploy.admin.user" value="admin"/>
<property name="deploy.admin.password" value="welcome1"/>
<property name="deploy.admin.host" value="c3app2002d"/>
<property name="deploy.admin.port" value="4884"/>
<property name="deploy.sunone.instance" value="ucm-dev"/>

<path id="deploy.sunone.path">
    <pathelement location="${deploy.sunone.home}/lib/dom.jar"/>
    <pathelement location="${deploy.sunone.home}/lib/xalan.jar"/>
    <pathelement location="${deploy.sunone.home}/lib/xercesImpl.jar"/>
    <pathelement location="${deploy.sunone.home}/lib/appserv-ideplugin.jar"/>
    <pathelement location="${deploy.sunone.home}/lib/appserv-rt.jar"/>
    <pathelement location="${java.home}/lib/tools.jar"/>
    <pathelement location="${deploy.sunone.home}/lib/j2ee.jar"/>
    <pathelement location="${deploy.sunone.home}/lib/appserv-ext.jar"/>
    <pathelement location="${deploy.sunone.home}/lib/appserv-cmp.jar"/>
    <pathelement location="${deploy.sunone.home}/lib/appserv-admin.jar"/>
</path>

<target name="verify-ear" depends="ear">
    <mkdir dir="${build}/doc"/>
    <java classname="com.sun.enterprise.tools.verifier.Verifier"
          fork="yes" failonerror="true">
        <env key="LD_LIBRARY_PATH" path="${deploy.sunone.home}/lib"/>
        <sysproperty key="java.ext.dirs" 
                     path="${java.home}/jre/lib/ext"/>
        <sysproperty key="com.sun.enterprise.home"
                     value="${deploy.sunone.home}"/>
        <sysproperty key="com.sun.aas.verifier.xsl"
                     value="${deploy.sunone.home}/lib/verifier" />
        <sysproperty key="com.sun.aas.installRoot" 
                     value="${deploy.sunone.home}" />

        <arg line="--destdir ${build}/doc"/>
        <arg line="--reportlevel warnings"/>
        <arg line="-C ${deploy.sunone.home}/lib/verifier"/>
        <arg value="${build}/ucmnetworkserver.ear" />
        <classpath refid="deps"/>
        <classpath refid="deploy.sunone.path"/>
    </java>
    <!--
    <exec executable="${deploy.sunone.home}/bin/verifier"
          timeout="120000">
        <arg value="${build}/ucmnetworkserver.ear" />
    </exec>
    -->
</target>

And now, an AIM URL!

This is quite cool: message me.

Using Tibco EMS with SunONE AS 7

If for some reason you are unfortunate enough to have to use SunONE Advanced Server 7 (variously known as as7se, SunONE AS7, Sun Java System Advanced Server, etc.) and have to integrate it with Tibco’s JMS server (Tibco EMS), here is what you need to do.

App server configuration

If for some reason you are unfortunate enough to have to use SunONE Advanced Server 7 (variously known as as7se, SunONE AS7, Sun Java System Advanced Server, etc.) and have to integrate it with Tibco’s JMS server (Tibco EMS), here is what you need to do.

App server configuration

Put the Tibco client JARs (jndi.jar, jms.jar and tibjms.jar) in $SUNONE/share/lib. Add them to the server’s classpath. In the server admin console, navigate to:

  • Server/JVM Settings/Path Settings/Class path suffix

Or directly in server.xml:

<server ...>
  ...
  <java-config ... classpath-suffix="C:\Sun\AppServer7\share\lib\jndi.jar;C:\Sun\AppServer7\share\lib\jms.jar;C:\Sun\AppServer7\share\lib\tibjms.jar" ...

Configure an “external JNDI” resource for the Queue Connection Factory:

  • Server/JNDI/External Resources

and set:

  • Resource Type: javax.naming.InitialContext
  • JNDI Lookup: QueueConnectionFactory
  • Factoryclass: com.tibco.tibjms.naming.TibjmsInitialContextFactory

and add a property:

  • java.naming.provider.url = tibjmsnaming://localhost:7222

Configure an “external JNDI” resource for the Queue:

  • Resource Type: javax.jms.Queue
  • JNDI Lookup: queue.sample
  • Factoryclass: com.tibco.tibjms.naming.TibjmsInitialContextFactory

and add a property:

java.naming.provider.url = tibjmsnaming://localhost:7222

If the initial JNDI lookup requires a username/password, this can be passed in by setting two additional properties:

  • java.naming.security.principal = username
  • java.naming.security.credentials = password

These can also be defined directly in server.xml:

<external-jndi-resource 
    enabled="true" 
	jndi-name="jms/tibco/QueueConnectionFactory" 
	res-type="javax.naming.InitialContext" 
	factory-class="com.tibco.tibjms.naming.TibjmsInitialContextFactory" 
	jndi-lookup-name="QueueConnectionFactory">

  <property 
	name="java.naming.provider.url"	
	value="tibjmsnaming://localhost:7222">

</external-jndi-resource>

<external-jndi-resource 
    enabled="true" 
	jndi-name="jms/tibco/queue.sample" 
	res-type="javax.jms.Queue" 
	factory-class="com.tibco.tibjms.naming.TibjmsInitialContextFactory" 
	jndi-lookup-name="queue.sample">

  <property 
	name="java.naming.provider.url"	
	value="tibjmsnaming://localhost:7222">

</external-jndi-resource>

Additional queue (and topic) resources can be added as required.

Bean Deployment

No changes are required to MDB code to support the Tibco provider. The deployment descriptor must be configured to use the external JNDI resources defined above.

In sun-ejb-jar.xml, the mdb-connection-factory should refer to the Queue connection factory defined above. Also, a resource reference should be defined refering to the factory. For example:

<pre>
<sun-ejb-jar>
   <enterprise-beans>
      <ejb>
         <ejb-name>TestBean</ejb-name>
         <jndi-name>jms/tibco/queue.sample</jndi-name>
		 <resource-ref>
            <res-ref-name>jms/QueueConnectionFactory</res-ref-name>
            <jndi-name>jms/tibco/QueueConnectionFactory</jndi-name>
         </resource-ref>
         <mdb-connection-factory>
            <jndi-name>jms/tibco/QueueConnectionFactory</jndi-name>
            <default-resource-principal>
               <name>sunone</name>
               <password>guest</password>
            </default-resource-principal>
         </mdb-connection-factory>
      </ejb>
   </enterprise-beans>
</sun-ejb-jar> 
</pre>

In ejb-jar.xml, make sure the transaction type is “Bean” (see Transactions below). The resource reference should refer to the resource defined in sun-ejb-jar.xml above. Note that the “res-ref-name” in these two files is independant of the JNDI name of the actual resource (that is, they must only match each other).

<pre>
<ejb-jar>
   <enterprise-beans>
      <message-driven>
         <ejb-name>TestBean</ejb-name>
         <ejb-class>au.com.sensis.c3.cca.connectors.ejb.TestBean</ejb-class>
         <transaction-type>Bean</transaction-type>
         <message-driven-destination>
            <destination-type>javax.jms.Queue</destination-type>
         </message-driven-destination>
         <resource-ref>
            <res-ref-name>jms/QueueConnectionFactory</res-ref-name>
            <res-type>javax.jms.QueueConnectionFactory</res-type>
            <res-auth>Container</res-auth>
         </resource-ref>
      </message-driven>
   </enterprise-beans>
</ejb-jar>
</pre>

Transactions

From: swforum.sun.com

Sun ONE Application Server 7 does not support CMT MDBs with 3rd party JMS providers. (As you note, BMT MDBs work with MQSeries and Sun ONE App Server).

As stated, bean-managed transactions appear to work fine (see sample code below).

Sample Bean

A sample implementation of a minimal message-driven bean that was tested with SunONE and Tibco EMS:

<pre>
package au.com.sensis.c3.cca.connectors.ejb;

import javax.ejb.EJBException;
import javax.ejb.MessageDrivenBean;
import javax.ejb.MessageDrivenContext;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
import javax.transaction.SystemException;
import javax.transaction.UserTransaction;


/**
 * A simple MDB that listens for messages and mentions the fact if it
 * receives one.
 *
 * @author <a href="mailto:mrowe@mojain.com">Michael Rowe </a>
 */
public class TestBean
    implements MessageDrivenBean, MessageListener
{

    protected static org.apache.commons.logging.Log _log =
        org.apache.commons.logging.LogFactory.getLog(TestBean.class);

	protected MessageDrivenContext _context;

    public TestBean()
    {
    }

    public void onMessage(Message message)
    {

        UserTransaction ut = _context.getUserTransaction();

        if (message instanceof TextMessage)
        {
            String doc = null;
            try
            {
				ut.begin();
                TextMessage text = (TextMessage) message;
				doc = text.getText();
				ut.commit();
             }
             catch (Exception e)
             {
				try
				{
					_log.warn("rolling back transaction");
					ut.rollback();
				}
				catch (SystemException se)
				{
					throw new EJBException("Rollback failed: " + se.getMessage());
				}
                _log.error("Can't get text", e);
             }
            _log.info("Got message: " + doc);
        }
        else
        {
            _log.warn("Message received, but it wasn't a TextMessage");
        }

    } // onMessage()

    public void ejbCreate()
    {
    }

    public void ejbRemove()
    {
    }

    public void setMessageDrivenContext(MessageDrivenContext context)
            throws EJBException
    {
		_context = context;
    }

}

SunONE mdb concurrency notes

Default pool settings allow mutiple beans to be created. They appear to be created (constructor/ejbCreate()) on demand, but then stay in the pool.

The onMessage() calls run on multiple threads, but there appears to be reuse (i.e. threads that have finished executing an onMessage() will be used again).

Setting the pool to have a max of one bean (bean-pool/max-pool-size) causes messages to be consumed serially by one instance of the bean, in one thread (as expected).

And interestingly (although not all that relevant), all the beans seem to get constructed by the same thread, and that thread doesn’t do any message servicing. But there does seem to be a 1:1 relationship between bean instances and message servicing threads (although this is with only six beans in the pool, it could be different with lots more).

If there is only one bean in the pool, it is still constructed on a different thread to that on which is processes messages.

We're putting the band back together!

Well, “band” may be over-selling it a little… Anyhow, Andy B and I had a bit of jam yesterday. Sad, really, how little actual ability I have to play the bass. Maybe I should practice some time. But at least it got me out of the house for a couple of hours, and it’s always good to stretch the brain cells in a different direction to usual.

Speaking of the home front, it has been an action packed couple of weeks, with Kian’s birthday and easter and all. He’s very excited about being four. Temma has also been developing in leaps and bounds, and is now crawling all over the house with great aplomb. She’d really prefer to be walking, I think, but will have to wait until she has a bit more balance.

Jo has uploaded tons of photos to the family website so be sure to check them out. I’ve upgraded the site to Drupal 4.4. This included a small patch to the image module to give me a block that lists image galleries, so the navigation has changed a little.

A patch to the Drupal image module

I wrote a small patch to the Drupal image module that provides a block to list the top-level image taxonomy terms (the set of galleries you get when you browse to .../image):

Index: modules/image/image.module
 ===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/image/image.module,v
retrieving revision 1.107
diff -u -r1.107 image.module
--- modules/image/image.module	7 Mar 2004 12:52:19 -0000	1.107
+++ modules/image/image.module	10 Apr 2004 01:43:02 -0000
@@ -66,6 +66,22 @@
 }
 
 /**
+ * Image block
+ */
+function image_block($op = "list", $delta = 0) {
+    // listing of blocks, such as on the admin/system/block page
+    if ($op == "list") {
+	$blocks[0]["info"] = t("Image Gallery");
+    } else {
+	// our block content
+	$blocks["subject"] = t(variable_get("image_block_title", "Image Galleries"));
+	$albums = taxonomy_get_children($tid, variable_get("image_nav_vocabulary", ""));
+	$blocks["content"] = theme_image_block($albums);
+    }
+    return $blocks;
+}
+
+/**
  * Node descriptor
  */
 function image_node_name() {
@@ -152,6 +168,8 @@
 
   $output .= form_select(t("Disable Image Caching"), "image_random_suffix",  \
	variable_get("image_random_suffix", "0"), array("0" => "disabled",    \
	"1" => "enabled"), t("Enabling this will add random parameters "      \
	. "to image URIs which will prevent the browser from caching."));
 
+  $output .= form_textfield(t("Title for image block"), "image_block_title", \
        variable_get("image_block_title", "Image Galleries"), 20, 80,         \
	t("The title to display in the image block, if enabled"));
+
   return $output;
 }
 
@@ -1401,6 +1419,18 @@
   return '<hr>' . theme("pager", $tags, $limit, $element, $attributes);
 }
 
+/**
+ * Theme function to render the image block contents
+ */
+function theme_image_block($albums) {
+    $output = "<div class=\"menu\"><ul>";
+    foreach ($albums as $album) {
+	$output .= "<li class=\"collapsed\">" . l($album->name, "image/tid/"  \
		   . $album->tid) . "</li>";
+    }
+    $output .= "</ul></div>";
+    return $output;
+}
+
 /****
 ***** admin function and friends
 ****/

A nice enhancement would be to allow control over the depth of taxonomy terms to include (that is, display child galleries in the block, to a user-defined level).