Red5 + Hibernate Revisited

Filed under Flash, Hibernate, Java, Red5, Software, Spring Framework, Tutorials

It’s hard to believe that I wrote the first version of this tutorial almost a year and a half ago. That’s too long to wait for an update in my opinion and is much needed in this case. My apologies for not tending to the garden sooner. While the original article illustrated a simple method for integrating Red5[1] and Hibernate[2], by today’s standards it’s design is overly verbose and somewhat out of fashion. Not to mention the 3 major components used in this tutorial have all gone through major revisions. The primary goal for this iteration was simplification of the code as well as the XML configuration elements and results in a smaller code footprint. This is a big win in my book.

*Note: If you are not familiar with my first post on this topic, please give it a read before continuing here.

Getting started, the only item left untouched by the refactoring is the User object. Given that I originally defined the class using the Hibernate annotations there is nothing to change other than to use the annotation based SessionFactory by default. I should note that the Hibernate mapping (HBM) file for the User object no longer exists in the source tree.

User.java

@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue
    @Column(name = "id", nullable = false)
    private Long id;

    @Column(name = "user_name", nullable = false, length = 32, unique = true)
    private String userName;

    @Column(name = "password", nullable = false)
    private String password;

    @Column(name = "first_name", length = 64)
    private String firstName;

    @Column(name = "last_name", length = 64)
    private String lastName;

    @Column(name = "email", nullable = false)
    private String email;

    ...
}

HibernateApplicationDAO has been given a new name and is now HibernateApplicationRepository. This is one of the biggest changes here and removes the dependency on Spring’s HibernateTemplate. HibernateTemplate was originally created to work around the limitations of Hibernate 2′s usage of checked exceptions. Because Hibernate now uses unchecked runtime exceptions, it is no longer necessary. Injection of the SessionFactory into the repository object gives us direct access to the current HibernateSession. The @Repository annotation has been added to support transactions and persistence exception translation.

HibernateApplicationRepository.java

@Repository
public class HibernateApplicationRepository implements ApplicationRepository {

    private static final Logger logger = Red5LoggerFactory.getLogger(HibernateApplicationRepository.class, "hibernate");

    private SessionFactory sessionFactory;

    public User getUser(String user, String pass) {
        logger.debug("Retrieving user from the database.");
        // Ask Hibernate to query for the user based upon the specified user/pass.
        Criteria crit = sessionFactory.getCurrentSession().createCriteria(User.class);
        crit.add(Restrictions.eq("userName", user));
        crit.add(Restrictions.eq("password", pass));
        User u = (User) crit.uniqueResult();
        // Insure that we got something back from Hibernate.
        if (null == u) {
            logger.warn("User does not exist for credentials: {}/{}", user, pass);
            throw new ObjectRetrievalFailureException(User.class, user);
        }
        // Return the results.
        return u;
    }

    @Required
    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }
}

ApplicationServiceImpl is now annotated with the @Service and @Transactional annotations. With these annotations, we no longer need to proxy ApplicationServiceImpl and can eliminate several lines of XML in the configuration file while maintaining full transaction support.

ApplicationServiceImpl.java

@Service
@Transactional
public class ApplicationServiceImpl implements ApplicationService {

    private static final Logger logger = Red5LoggerFactory.getLogger(ApplicationServiceImpl.class, "hibernate");

    private ApplicationRepository repository;

    @Transactional(readOnly = true)
    public User getUser(String user, String pass) throws ServiceException {
        logger.debug("Looking up user for user/pass of: {}/{}", user, pass);
        try {
            // Return the result of the data access call.
            return repository.getUser(user, pass);
        }
        catch (Exception e) {
            logger.error("Could not load user with credentials: {}/{}", user, pass);
            throw new ServiceException("Could not load user!", e);
		}
	}

    @Required
    public void setApplicationRepository(ApplicationRepository repository) {
        this.repository = repository;
    }
}

As you can see from the following bean declaration, we’ve cut down on the XML configuration significantly by taking advantage of these annotations.

red5-web.xml

    <tx:annotation-driven/>

    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>

    <bean id="applicationRepository" class="net.sziebert.tutorials.dao.hibernate.HibernateApplicationRepository">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>

    <bean id="applicationService" class="net.sziebert.tutorials.service.impl.ApplicationServiceImpl">
        <property name="applicationRepository" ref="applicationRepository"/>
    </bean>

For the sake of direct comparison, here is the original bean declaration:

red5-web.xml

    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

    <bean id="txProxyTemplate" abstract="true" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="transactionManager">
            <ref local="transactionManager" />
        </property>
        <property name="transactionAttributes">
            <props>
                <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
                <prop key="save*">PROPAGATION_REQUIRED</prop>
                <prop key="update*">PROPAGATION_REQUIRED</prop>
                <prop key="delete*">PROPAGATION_REQUIRED</prop>
            </props>
        </property>
    </bean>

    <bean id="applicationDAO" class="net.sziebert.red5.adapter.dao.hibernate.HibernateApplicationDAO">
        <property name="hibernateTemplate" ref="hibernateTemplate" />
    </bean>

    <bean id="applicationService" parent="txProxyTemplate">
        <property name="target">
            <bean class="net.sziebert.red5.adapter.service.impl.ApplicationServiceImpl">
                <property name="applicationDAO" ref="applicationDAO" />
            </bean>
        </property>
    </bean>

All classes now use the improved Red5 logging support which is backed by SLF4J and Logback by default. Here is an example from the ApplicationAdapter subclass:

Application.java

public class Application extends ApplicationAdapter {

    private static final Logger logger = Red5LoggerFactory.getLogger(Application.class, "hibernate");

    private ApplicationService service;

    /* ----- ApplicationAdapter delegate methods ----- */

    @Override
    public boolean roomConnect(IConnection conn, Object[] params) {
        logger.debug("New connection attempt from {}...", conn.getRemoteAddress());
        // Parse the user/pass out of the connection parameters
        String userName = (String) params[0];
        String password = (String) params[1];
        // Get the User object for this connection
        User user = null;
        // Insure that we have received the proper set of parameters.
        if (isNotBlank(userName) &&
                isNotBlank(password)) {
            user = authenticate(userName, password);
        }
        // If we got a valid user object, then allow the connection. Otherwise,
        // the user could not be found or something bad happened. In either
        // case, we do not want to allow the connection.
        return user != null && super.roomConnect(conn, params);
    }

    ...
}

The flash client code has been rewritten entirely in ActionScript 3 and was reduced down to a single class for the entire application. For those of you following along with the source, you’ll note that there are actually 2 classes. The second one is a simple dynamic class used to store runtime related resources and could be easily removed from the implementation. It was a design decision on my part to keep it. ActionScript 3 is much more object-oriented than it’s predecessor and leverages the new keyword for constructing components.

Hibernate.as

package net.sziebert.tutorials {

	import com.adobe.crypto.MD5;

	import fl.controls.Button;
	import fl.controls.Label;
	import fl.controls.TextArea;
	import fl.controls.TextInput;
	import fl.managers.StyleManager;
	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.events.NetStatusEvent;
	import flash.events.SecurityErrorEvent;
	import flash.net.NetConnection;
	import flash.net.ObjectEncoding;
	import flash.net.registerClassAlias;
	import flash.text.TextFieldAutoSize;
	import flash.text.TextFormat;

	import net.sziebert.tutorials.Runtime;

	public class Hibernate extends Sprite {

		private var connect:Button;
		private var pass:TextInput;
		private var passLbl:Label;
		private var runtime:Runtime;
		private var textArea:TextArea;
		private var user:TextInput;
		private var userLbl:Label;

		public function Hibernate():void {
			trace("Starting Hibernate application...");
			runtime = Runtime.getInstance();
			stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.addEventListener(Event.RESIZE, onResize);
			createChildren();
		}

		/* ----- Utility functions ----- */

		private function createChildren():void {
			// Create the username label.
			userLbl = new Label();
			userLbl.text = "Username:";
			userLbl.autoSize = TextFieldAutoSize.LEFT;
			addChild(userLbl);
			// Create the username input field.
			user = new TextInput();
			user.maxChars = 255;
			user.restrict = "A-Za-z 0-9:;()|.,?!$&*{}[]+=_-'"";
			addChild(user);
			// Create the password label,
			passLbl = new Label();
			passLbl.text = "Password:";
			passLbl.autoSize = TextFieldAutoSize.LEFT;
			addChild(passLbl);
			// Create the password input field.
			pass = new TextInput();
			pass.maxChars = 255;
			pass.displayAsPassword = true;
			addChild(pass);
			// Create the connect button.
			connect = new Button();
			connect.label = "Connect";
			connect.addEventListener(MouseEvent.CLICK, onClick);
			addChild(connect);
			// Create the text area to display the log information.
			textArea = new TextArea();
			textArea.editable = false;
			textArea.wordWrap = true;
			addChild(textArea);
			// Remove the avatar.
			removeChildAt(0);
			// Size the elements.
			setSize(stage.stageWidth, stage.stageHeight);
		}

		private function setSize(w:Number, h:Number):void {
			// Move and size the username components
			userLbl.move(18, 18);
			//userLabel.setSize(50, 22);
			user.move(96, 18);
			user.setSize((w - 226), 22);
			// Move and size the password components
			passLbl.move(18, 50);
			//passLabel.setSize(50, 22);
			pass.move(96, 50);
			pass.setSize((w - 226), 22);
			// Move and size the connect button
			connect.move(w - 118, 18);
			connect.setSize(100, 54);
			// Move and size the textarea
			textArea.move(18, 82);
			textArea.setSize((w - 36), (h - 100));
		}

		private function initConnection(username:String, password:String):void {
			if (username == "" || password == "") {
				throw new Error("You must enter a valid username and/or password.");
			}
			// Create the new connection object.
			runtime.conn = new NetConnection();
			runtime.conn.addEventListener(NetStatusEvent.NET_STATUS, onNetStatus);
			runtime.conn.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError);
			log("Connecting to Red5...");
			runtime.conn.connect("rtmp://localhost/hibernate/test", username, MD5.hash(password));
		}

		private function log(msg:String):void {
			// Update the chat window with the message
			textArea.text += (msg + "n");
			textArea.verticalScrollPosition = textArea.maxVerticalScrollPosition;
		}

		/* ----- Event handlers ----- */

		private function onResize(event:Event):void {
			setSize(stage.stageWidth, stage.stageHeight);
		}

		private function onClick(event:MouseEvent):void {
			var button:Button = event.target as Button;
			// We are currently connected, so disconnect.
			if (runtime.conn && runtime.conn.connected) {
				runtime.conn.close();
				return;
			}
			try {
				// Initialize the connection
				initConnection(user.text, pass.text);
			} catch (err:Error) {
				// Otherwise we need to show an alert indicating the need for proper user input.
				log("Error: You must enter a valid username and/or password.");
			}
		}

		private function onNetStatus(event:NetStatusEvent):void {
			trace("onNetStatus: " + event.info.code);
			switch (event.info.code) {
			        case "NetConnection.Connect.Success":
					log("Connection attempt successful.");
					connect.label = "Disconnect";
                                        break;
				case "NetConnection.Connect.Rejected":
					log("Connection attempt rejected.");
					break;
				case "NetConnection.Connect.Closed":
					log("Connection closed.");
					connect.label = "Connect";
					break;
				case "NetConnection.Connect.Failed":
					log("Connection failure.");
					break;
		        }
		}

		private function onSecurityError(event:SecurityErrorEvent):void {
        		trace("onSecurityError: " + event);
        	}
	}
}

Lastly, the tutorial now relies upon Ant and Ivy to define and retrieve the necessary dependencies. There’s nothing terribly magical about it, the build script should take care of everything for you. Keep in mind that the Red5 JAR dependency is pulled directly from the continuous integration server and should be up to date with the latest found in the Subversion repository at googlecode. If you aren’t running on the latest from trunk, you should strongly consider replacing this JAR with the one found in your Red5 install.

ivy.xml

<ivy-module version="2.0"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:noNamespaceSchemaLocation="http://ant.apache.org/ivy/schemas/ivy.xsd">
    <info organisation="garagetech" module="tutorials"/>
    <configurations defaultconfmapping="default->*">
        <conf name="compile"/>
        <conf name="default"/>
        <conf name="test" extends="default" description="Unit testing dependencies."/>
    </configurations>
    <dependencies>
        <dependency name="commons-dbcp" org="commons" rev="1.2.2"/>
        <dependency name="commons-lang" org="commons" rev="2.4" conf="compile->*"/>
        <dependency name="dom4j" rev="1.6.1"/>
        <dependency name="ejb3-persistence" rev="" conf="compile->*"/>
        <dependency name="hibernate-annotations" org="hibernate" rev="3.4.0"/>
        <dependency name="hibernate-commons-annotations" org="hibernate" rev="3.4.0"/>
        <dependency name="hibernate-entitymanager" org="hibernate" rev="3.4.0"/>
        <dependency name="hibernate-validator" org="hibernate" rev="3.1.0"/>
        <dependency name="hibernate" org="hibernate" rev="3.3.1"/>
        <dependency name="javassist" rev="3.4"/>
        <dependency name="mysql-connector-java" rev="5.0.6-bin"/>
        <dependency name="red5" rev="" changing="true" conf="compile->*"/>
        <dependency name="slf4j-api" rev="1.5.6" conf="compile->*"/>
        <dependency name="spring-aop" org="spring" rev="2.5.6" conf="compile->*"/>
        <dependency name="spring-beans" org="spring" rev="2.5.6" conf="compile->*"/>
        <dependency name="spring-context" org="spring" rev="2.5.6" conf="compile->*"/>
        <dependency name="spring-core" org="spring" rev="2.5.6" conf="compile->*"/>
        <dependency name="spring-jdbc" org="spring" rev="2.5.6"/>
        <dependency name="spring-orm" org="spring" rev="2.5.6"/>
        <dependency name="spring-tx" org="spring" rev="2.5.6"/>
    </dependencies>
</ivy-module>

That covers the significant changes to the tutorial code. As with the first version, you’ll need to configure MySQL to allow Red5 to talk to it. (Notes on this can be found in the README file with the examples.) You’ll also need to add your user data to the tables once Hibernate has generated the schema.

The example source code for this post can be downloaded here.

1. http://osflash.org/red5 For those that aren’t up on Red5, it is a robust Flash Media Server alternative written entirely in Java that supports multi-user video chat, video streaming and real-time, multi-player gaming.

2. http://hibernate.org Hibernate lets you develop persistent classes following object-oriented idiom – including association, inheritance, polymorphism, composition, and collections. It allows you to express queries in its own portable SQL extension (HQL), as well as in native SQL.

3. http://springframework.org Spring is a layered Java/JEE application framework founded on the simple concepts that any tool should be a pleasure to use, that your application code should not depend on Spring APIs and that it should not compete with existing solutions, but should foster integration.

4. http://mysql.com The MySQL database has become the world’s most popular open source database because of its consistent fast performance, high reliability and ease of use.

Liked this post? Share it with others:
  • Digg
  • del.icio.us
  • Facebook
  • DZone
  • email
  • LinkedIn
  • Reddit
  • Slashdot
  • StumbleUpon
  • Technorati
  • TwitThis

14 Comments

  1. Posted January 30, 2009 at 4:16 pm | Permalink

    Awesome post, as expected! :)

  2. Posted January 31, 2009 at 12:10 am | Permalink

    Thank you so much, this is MORE than expected :D … Beautiful X 10000

  3. Vardan
    Posted May 5, 2009 at 4:41 am | Permalink

    Hi Carl,

    It’s a great article, thanks.
    A question if you could help me please?
    Is it possible to stream a video from db rather than from /streams directory? Lets say I have longblob object(my flv object) in mysql and I want to stream it through red5, is it possible, if so how, could you help me please?

    Thanks.

  4. Posted May 5, 2009 at 4:53 am | Permalink

    Vardan, why in the name of greatness will you do that ?

  5. Vardan
    Posted May 7, 2009 at 4:54 am | Permalink

    Iongion, the reason I need is stream videos from database rather than from directories. The direectories can be curropted, deletion prone, etc… but db is secure, so that’s why I would like to stream from db if it’s possible.

  6. Posted May 7, 2009 at 7:28 am | Permalink

    Vardan:

    Consider than the average FLV consumes about 1 MB of space per minute of video. Given that, your DB would need to be quite robust and would need to be served on a box with an incredible amount of RAM. Not to mention, it is a naive assumption that your DB is more secure than the file system. The data in a DB is just as susceptible to corruption.

  7. Vardan
    Posted May 7, 2009 at 3:59 pm | Permalink

    Hi Carl,

    Thanks for your reply. I believe you what you are saying and your opinion, I appreciated, but my question is it possible to stream from a db rather than a file system, if so, how? How can I play Bytearray flv object which I read from a db table and play in my Flex page?

    Thanks an advance.

    Vardan

  8. Posted May 8, 2009 at 1:03 am | Permalink

    Vardan, do not do it like this, no one will tell you how to do it as no one does it like you, it is plain and simple WRONG !!!

  9. Vardan
    Posted May 8, 2009 at 2:39 pm | Permalink

    Thanks guys for your help.

  10. Posted May 9, 2009 at 6:12 pm | Permalink

    Vardan:

    I don’t want to dismiss your problem, though streaming an FLV from a database isn’t possible with Red5. While you could record the FLV to disk, then copy it into your database, I still wouldn’t recommend it. Given the size issue I mentioned previously, you have to give consideration to the resources used in something like this. You’d need to hold open a connection to your database indefinitely in the case where a user paused the FLV playback. It really just isn’t feasible.

  11. Vardan
    Posted May 12, 2009 at 1:26 pm | Permalink

    Carl,

    Thanks alot for your valuable feedback. I appreciate.
    The reason I was asking by thinking if it’s possble to stream from a file system why isn’t possible to stream from a db and also your point about user pausing, forwarding, etc… isn’t good idea using the db.
    I was just curious.

    Thanks again for your help.

    P.S. Great article.

    Regards

    Vardan

  12. cem
    Posted January 25, 2010 at 2:16 pm | Permalink

    Hi Carl
    This is a great post, thank you so much.
    İs it possible to add some information how to run the application in a specific IDE (e.g eclipse) with the step by step. I have looked at the Readme file, but couldnT any information about this issue.
    In the application, u have used many tech. and it is a bit hard to understand the flow for a new user to these tech, like me :S

    Regards
    Cem

  13. Posted January 26, 2010 at 10:13 am | Permalink

    Hello again after so much time.

    Is it possible to update this tutorial to use JPA with thin backend such as EclipseLink (to reduce dependency number and be easy for us starters)

  14. Yevgeny
    Posted February 10, 2011 at 12:09 pm | Permalink

    Hey Carl,

    Thank you very much. if it was not for you I would not even have red5 up and running with hibernate. This article was amazing. I was actually hoping to add to the same request as iongion. All other tutorials on the internet seem slim, and yours is so comprehensive, I would love to see how to refactor this into a JPA project.

4 Trackbacks

  1. [...] many of you want to use an ORM layer with Red5, I get direct emails all-the-time! Please look at Carl’s tutorial instead. var dzone_style=”2″;View this Post [...]

  2. By spring2 + annotations + DAO verst on May 14, 2009 at 9:47 pm

    [...] Funktionen in der Klasse User direkt an? Bei dem Beispiel-Code handelt es sich um die hier : http://sziebert.net/posts/red5-hibernate-revisited/ zu findende Implementierung von Hibernate in einen bestehenden Mediaserver. Ich bin f

  3. [...] Funktionen in der Klasse User direkt an? Bei dem Beispiel-Code handelt es sich um die hier : http://sziebert.net/posts/red5-hibernate-revisited/ zu findende Implementierung von Hibernate in einen bestehenden Mediaserver. Ich bin f

  4. By hibernate - StartTags.com on January 25, 2010 at 10:11 pm

    [...] @ StartTags Carl Sziebert Red5 + Hibernate Revisitedis a software engineer with an interest in Spring, Hibernate, Red5 and jQuery development.capricious [...]