RSS User IDs

Well, I guess I wasn’t paying close attention to the feedback on Tim Bray’s counting RSS subscribers - Greg Reinacker describes a method for automatically assigning RSS user IDs (and Derek Scruggs’ work on counting RSS subscribers). I have my misgivings, but it looks promising.

Derek has concerns about aggregator support for HTTP 301 redirects and ETag headers. I’d like to add aggregator support for user IDs to the list. It’s the wild west in RSS and aggregators right now and people will be hopping around for a while. So we need to easily transfer user IDs from aggregator to aggregator. And we need to balance that against the ability to refer others to RSS feeds without piggy backing on existing user IDs.

Plus, I think that people will always want a safety net of anonymity. So it might be nice to reserve a standard block of IDs for that purpose - an aggregator could then be configured to randomly use an ID from that block for a given feed.

I still think that email marketing has a lot of life left in it. But maybe RSS is ready to take on more of the load than I had thought.

Marketing by RSS

I love RSS as much as anyone else, but we don’t do anyone any favors when we refuse to take off the rose-colored sunglasses. Chris Pirillo throws some brickbats at an RSS doubter, but I happen to agree with the doubter on several points:

  • You can’t reliably measure exposure via RSS.
  • You can’t control how RSS is displayed.
  • RSS doesn’t build a user database.
  • RSS is difficult to customize - as a response driver - the way email is.

Tim Bray brought up the issue of RSS Subscriber Counting before, but the nay-sayers shut down the conversation before we had a good answer. The most valuable subscribers are the most likely to be sitting behind a proxy server and the most likely to be lost in the shuffle. Just how is RSS going to address that?

Maybe I’m dense. But I don’t see how anyone is going to build a RSS user database, segment the users and target the segments independently - remembering that a user may belong to multiple segments. I happen to believe in 1-to-1 marketing, how does RSS fit into that world?

And don’t expect to control presentation in my RSS aggregator. I don’t think one-page aggregators are going away and I don’t think anyone is up to the job of blending dozen’s of presentation formats on the same page.

Now, maybe spam spells the end of email marketing. But I think email is important enough that we’ll fix it before it dies. And that we’ll see RSS become one of the tools in marketing’s toolchest - supplementing email, not replacing it.

Hibernate Prototyping with the BeanShell

Just playing around is one of my favorite ways of becoming familiar with software. Let’s see how we can use the BeanShell to prototype Hibernate. I’m partial towards the BeanShell because it’s embedded in my Java Development Environment for Emacs. But if you’re so inclined, then Jython will probably do as well. Just in case there is a lurking configuration gotcha, here’s my current Java software configuration.

I must confess that I wasn’t planning on using the BeanShell. I was planning on adding a simple interpreter with java.io.StreamTokenizer to allow interactive manipulation of Keyword and KeySet objects. And then I realized that by setting the fork attribute of the java task to true separated my application from the console input. And setting it to false terminates the application with an java.lang.ExceptionInInitializerError. The BeanShell was my ticket out.

BeanShell support requires just a handfull of minor changes to our build file. First, the BSH property is added to reference the BeanShell jar and it is prepended to classpath.run to form classpath.bsh. Second, a new bsh target is added in parallel to the run target – note that we use class bsh.Console rather than bsh.Interpreter because we still lose access to console input because of the JVM fork. And Finally, the dist target was modified to include a bsh sub-directory.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<project default="all">
<!-- ... -->
<property name="LOG4J" location="/Projects/Java/Lib/log4j.jar" />
<property name="BSH" location="/Projects/Java/Lib/bsh.jar" />

<!-- ... -->
<path id="classpath.bsh">
<pathelement location="${BSH}" />
<path refid="classpath.run" />
</path>

<!-- ... -->
<target name="setup-run" depends="clean-run">
<condition property="LOG4J_OUT" value="log4j-to-stdout.properties">
<istrue value="${TALK}"/>
</condition>
<condition property="LOG4J_OUT" value="log4j-to-file.properties">
<isfalse value="${TALK}"/>
</condition>
</target>
<target name="run" depends="compile,setup-run">
<java classname="Main"
fork="true">

<classpath refid="classpath.run" />
<sysproperty key="log4j.configuration" value="${LOG4J_OUT}" />
</java>
</target>
<target name="bsh" depends="compile,setup-run">
<java classname="bsh.Console"
fork="true">

<classpath refid="classpath.bsh" />
<sysproperty key="log4j.configuration" value="${LOG4J_OUT}" />
</java>
</target>
<target name="clean-run">
<delete verbose="${TALK}" file="hibernate.log" />
</target>

<!-- ... -->
<target name="dist">
<zip destfile="distrib.zip"
basedir="."
includes="build.xml,src/**,cfg/**,bsh/**"
excludes="src/prj.el"
/>

</target>
<!-- ... -->

</project>

Next, we’ll create a Scripted Object to simplify our interaction with hibernate. The Hibernate method returns an object that provides easy access to the most common functionality: open, save, find, delete and close.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import net.sf.hibernate.cfg.Configuration;
import net.sf.hibernate.SessionFactory;
import net.sf.hibernate.Session;
import net.sf.hibernate.Transaction;
import net.sf.hibernate.MappingException;
import net.sf.hibernate.HibernateException;

Hibernate() {
Configuration cfg = new Configuration();
cfg.addClass( hb.Keyword.class );
cfg.addClass( hb.KeySet.class );
SessionFactory factory = cfg.buildSessionFactory();
Session session = factory.openSession();

void open() {
session = factory.openSession();
}

void close() {
session = null;
}

void save( Object o ) {
session.saveOrUpdate( o );
}

void delete( Object o ) {
session.delete( o );
}

Transaction transaction() {
return session.beginTransaction();
}

Object findByName( String className, String objName )
{

java.util.List existing;
if ( objName == null || objName.length()==0 ) {
existing = session.find( "from " + className );
}
else {
existing = session.find( "from " + className +
" as o where o.name='" + objName + "'" );
}

if ( existing.isEmpty() ) {
return null;
}
else {
return existing.get(0);
}
}

return this;
}

Finally, two methods were added each of the KeySet and Keyword classes. The KeySet class gained an add and a remove method that simply provide access to the corresponding methods of the java.util.Set member variable. And the Keyword class gained methods for hashCode and equals – this insures that Keyword objects are equivalent when their String member variables are equivalent.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class Keyword {
//...
/**
* Hashcode generation
*/

public int hashCode()
{

return ( this.name == null ) ? 0 : this.name.hashCode();
}

/**
* Equality test
* @param other The Object to be checked for equality
*/

public boolean equals(Object other) {
boolean equality = false;

if ( this.getClass() == other.getClass() ) {
Keyword otherWord = (Keyword) other;

if ( this.id != unset_value && this.id == otherWord.id ) {
equality = true;
}
else if ( this.name.equals( otherWord.name) ) {
equality = true;
if ( this.id == unset_value ) {
this.id = otherWord.id;
}
else if ( otherWord.id == unset_value ) {
otherWord.id = this.id;
}
}
}

return equality;
}
// ...
}

And now we’re ready to play with the BeanShell (starting with an empty database). By the way, if you’re anything like me, then the number one thing to remember is use print rather than println.

BeanShell Desktop

We can use the BeanShell to see that we cannot use Hibernate for immutable objects need to be careful when comparing objects from different sessions:

1
1.3b2 - by Pat Niemeyer (pat@pat.net)
bsh % source("bsh/Hibernate.bsh");
bsh % import hb.*;
bsh % hb = Hibernate();
bsh % x = hb.findByName("Keyword","this");
bsh % y = hb.findByName("Keyword","this");
bsh % hb.close();
bsh % hb.open();
bsh % z = hb.findByName("Keyword","this");
bsh % hb.close();
bsh % print( (x==y) ? "equal" : "not-equal" );
equal
bsh % print( (x==z) ? "equal" : "not-equal" );
not-equal
bsh %

We also see that Hibernate performs a shallow save rather than a deep save:

21 May 2004: This behavior is controlled by the cascade attribute.

1
1.3b2 - by Pat Niemeyer (pat@pat.net)
bsh % source("bsh/Hibernate.bsh");
bsh % import hb.*;
bsh % hb = Hibernate();
bsh % tx = hb.transaction();
bsh % a = new Keyword("red");
bsh % s = new KeySet("colors");
bsh % s.add(a);
bsh % print(s);
{-1:colors|[-1:red]}
bsh % hb.save(s);
bsh % tx.commit();
// Error: // Uncaught Exception: Method Invocation tx.commit :
                                 at Line: 1 : in file: <unknown file>
                                 : tx .commit ( )

Target exception: net.sf.hibernate.TransientObjectException:
                                 object references an unsaved transient instance
                                 - save the transient instance before flushing

net.sf.hibernate.TransientObjectException:
                                 object references an unsaved transient instance
                                 - save the transient instance before flushing
	at net.sf.hibernate.impl.SessionImpl.getEntityIdentifierIfNotUnsaved (SessionImpl.java:2357)
	at net.sf.hibernate.type.EntityType.getIdentifier(EntityType.java:55)
	at net.sf.hibernate.type.ManyToOneType.nullSafeSet(ManyToOneType.java:33)
	at net.sf.hibernate.collection.CollectionPersister.writeElement(CollectionPersister.java:458)
	at net.sf.hibernate.collection.Set.writeTo(Set.java:226)
	at net.sf.hibernate.collection.CollectionPersister.recreate(CollectionPersister.java:692)
	at net.sf.hibernate.impl.ScheduledCollectionRecreate.execute(ScheduledCollectionRecreate.java:23)
	at net.sf.hibernate.impl.SessionImpl.executeAll(SessionImpl.java:2101)
	at net.sf.hibernate.impl.SessionImpl.execute(SessionImpl.java:2077)
	at net.sf.hibernate.impl.SessionImpl.flush(SessionImpl.java:2017)
	at net.sf.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:57)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:324)
	at bsh.Reflect.invokeOnMethod(Unknown Source)
	at bsh.Reflect.invokeObjectMethod(Unknown Source)
	at bsh.Name.invokeMethod(Unknown Source)
	at bsh.BSHMethodInvocation.eval(Unknown Source)
	at bsh.BSHPrimaryExpression.eval(Unknown Source)
	at bsh.BSHPrimaryExpression.eval(Unknown Source)
	at bsh.Interpreter.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:554)
bsh % tx.rollback();
bsh % hb.close();

We’re done for now. You’re ready to download the source to prototype Hibernate with the BeanShell and play.

Disclaimer: I don’t claim to be an expert on hibernate. Please send comments and corrections.

Where were you when the lights went out?

I was fortunate enough to ride out the entire blackout of ‘03 from the comfortable confines of home – Fairfield County, Connecticut. My girlfriend usually has an easy counter-commute home, but the lack of signals turned it into a slow slog. We had some peanut butter sandwiches for dinner with fresh strawberries for dessert. Then after reading by battery powered lantern for a bit we called it a night.

Our power came back around 4 or 5 am, but there are a lot of people in southwestern CT who are still without power. Looks like it’s going to be a lazy day.

Drawing the Line

One of the hardest tasks in developing new systems is knowing where to draw the line - include too much and you’ll never finish, include too little and all you have is a toy. So I think that Joi is being a bit harsh in his guess that there were people who were voicing concerns who had more vision. The problem lies in separating the true visionaries from the false prophets. SMTP and IPv4 have had a pretty good run over the past few decades - not too bad for what were essentially 0.x releases.

Now if Joi wants to throw some brickbats at those who deny that there is a problem or those who stand in the way of improvement while waiting for a perfect solution, then the line forms behind me.

Fab 5

It comes as a complete surprise, but Queer Eye for the Straight Guy has earned a spot on my weekly viewing schedule. The prospect of watching 5 gay guys (the Fab 5) make over a straight guy didn’t resonate with me at first. But I gave it a try based upon good reviews from friends and now I’m hooked.

Now, the initial visit to the bachelor digs with the requisite snide deconstruction of the wardrobe and decor has gotten old. But the show picks up once the Fab 5 start reconstructing - a product placement nirvana. And watching the big event with the Fab 5 in the peanut gallery is a hoot.

A new entry on my list of guilty pleasures. Of course, as a Tri-State resident, the real guilty pleasure is daydreaming about what they could do with me.

Welcome to the Neighbloghood

I was doing the usual obsessive compulsive review of my referrals when I came across fredshouse.net. Gene and I go waaay back, and I’m happy to welcome him to the blogroll (maybe this will get him off the snide at Technorati).

In other blogroll news, the BallBlog is joining the blog gang at FanBlogs. I think that we’re going to see more blogging confederations as individuals find themselves unable to write as frequently as they’d like.

Maybe it’s a function of the tools, but I’m surprised that I don’t see more confederations formed by a central blog linking to contributor blogs. It’s hard for an individual to maintain critical mass over multiple categories. But a group can easily maintain an on-topic critical mass while letting the individuals maintain a more eclectic style at home.

Three Laws of Nonsense

From Column two, a reminder of why we need to understand stakeholder perspectives before reaching conclusions:

  1. The source of nonsense is that for every piece of nonsense there exists an irrelevant frame of reference in which the item is sensible.
  2. The persistance of nonsense comes from rigorous arguments from inapplicable assumptions.
  3. The diffusion of nonsense results from the fact that people are more specialist than problems.

Full Posts vs Headlines

Sean Bonner would like for everyone to change your RSS feed [to] display your WHOLE post. I think that Sean is talking to the wrong audience - he should be appealing to the NewsReader developers.

I choose to make the full content of Take the First Step is available in my RSS feeds. But if someone has made a conscious decision to have excerpts rather than the full post in their feed, then that’s fine by me. In fact, if their website is part of their brand, then that’s the right choice for them.

What Sean really needs is a NewsReader that acknowledges that we live in a world of Always drifting between On and Off. A reader that would let him mark articles to be downloaded and read at a later time. And eventually, a Reader with predictive capability that could pre-fetch articles for you.

via Lockergnome’s RSS Resource.

A Persistent Collection with Hibernate

Our series on Hibernate continues with the addition of a Hibernate collection. The collection is a basic building block in programming and storing Keyword objects inside a KeySet collection object gives us an easy way to query and display our persistent objects.

The first step in implementing our KeySet class is deciding whether there will be a one-to-many or a many-to-many relationship between KeySet and Keyword objects. A Keyword object may not be contained in multiple KeySet objects in a one-to-many relationship and it may be contained in multiple KeySet objects in a many-to-many relationship.

This example will use a many-to-many relationship. This requires a new table with two foreign keys tying KeySets and Keywords rows together – a one-to-many relationship would require a foreign key in the Keywords table that referenced the KeySets table. The KeySet.hbm.xml file follows (comments show the SQL used to create the respective tables). It is quite similar to the Keyword.hbm.xml file with the addition of the set tag.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<hibernate-mapping>
<!-- CREATE TABLE KEYSETS ( ID IDENTITY, NAME VARCHAR(25) );
-->

<class name="hb.KeySet"
table="keysets">


<id name="id"
type="integer"
column="id"
unsaved-value="-1">

<generator class="identity"/>
</id>

<property name="name"
column="NAME"
not-null="true"
unique="true"
/>


<!-- CREATE TABLE KEYSET_WORD ( SET_ID INTEGER NOT NULL,
KEY_ID INTEGER NOT NULL,
PRIMARY KEY ( SET_ID, KEY_ID ),
FOREIGN KEY ( SET_ID ) references KEYSETS(ID),
FOREIGN KEY ( KEY_ID ) references KEYWORDS(ID) );
-->

<set name="keys"
lazy="false"
table="keyset_word">

<key column="set_id"/>
<many-to-many class="hb.Keyword"
column="key_id"
/>

</set>

</class>
</hibernate-mapping>

We will skip the KeySet.java class as it is primarily boilerplate get/set methods and is quite similar to the Keyword.java class. However, please note that there are some changes to our Keyword implementation:

  1. The parameter definition unsaved-value=”-1” has been added to Keyword.hbm.xml to explicitly declare an unsaved id value. And the static final int unsaved_value = -1 has been added to the Keyword java file and is used to set the initial value of the id member value. The two values need to match and should correspond to an invalid id value.
  2. The default constructor and the setId function are now protected. Hibernate sets the id when an object is loaded or saved and everything else should leave it alone.
  3. A toString() method has been added for easy output.

In addition, the build.xml file has been modified to add a DEBUG property that defines the value of the debug attribute of the javac task. The DEBUG property is set to true and Java source is now compiled with debug information.

Now let’s take a look at some code to load, query and create persistent objects. First, don’t forget to add the class to the configuration object. The error message “net.sf.hibernate.QueryException: unexpected token: as [from KeySet as ks …]” can be a bit perplexing until you’ve encountered it a few times.

1
2
3
4
5
6
Configuration cfg = new Configuration();
cfg.addClass( hb.Keyword.class );
cfg.addClass( hb.KeySet.class );

SessionFactory sessions = cfg.buildSessionFactory();
Session session = sessions.openSession();

Second, let’s query for a KeySet object.

1
2
tx = session.beginTransaction();
List existing = session.find( "from KeySet as ks where ks.name='colors'" );

If we find the KeySet we’re looking for, then we print it. Otherwise, we create a KeySet object, populate it with two Keyword objects and persist all three objects.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
if ( ! existing.isEmpty() ) {
ks = (KeySet) existing.get(0);
System.out.println( "Read " + ks );
}
else {
Set set = new HashSet();

kw = new Keyword( "red" );
session.save( kw );
set.add( kw );

kw = new Keyword( "blue" );
session.save( kw );
set.add( kw );

ks = new KeySet( "colors" );
ks.setKeys( set );
session.save( ks );

System.out.println( "Create " + ks );
}
tx.commit();

Now, start with a clean database and execute twice:

1
$ ant run
Buildfile: build.xml

init:

compile:

clean-run:

setup-run:

run:
     [java] Create {0:colors|[1:blue, 0:red]}

BUILD SUCCESSFUL
Total time: 14 seconds
$ ant run
Buildfile: build.xml

init:

compile:

clean-run:
   [delete] Deleting: /Projects/Learn/Hibernate/Simple/hibernate.log

setup-run:

run:
     [java] Read {0:colors|[0:red, 1:blue]}

BUILD SUCCESSFUL
Total time: 13 seconds

Success, we’ve stopped adding persistent objects to the database with every execution. And we’ve stored and retrieved objects. You can download the source for this simple Hibernate collection. As a reminder, here is my hibernate software configuration.

Disclaimer: I don’t claim to be an expert on hibernate. Please send comments and corrections.