<< Previous | Home

First experiments with the installed Red5 server.

Pimp some Spring beans in the Red5 server installation

First experiments with the installed Red5 server.

The oflaDemo streams from data files inside the exploded war. In this experiment i want to stream from outside the Red5 installation. Take a deep breath and dive into the code of the Red5 server:

Some small changes in DefaultStreamFilenameGenerator and the class is configurable via Spring: First i refactored the hardwired local variable "streams/" into a field to make to make the prefix configurable and added an appropriate setter.
    private String prefix = "streams/";
...
    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }
Next was to refactor the hardwired absolute path flag:
    public boolean resolvesToAbsolutePath() {
        return false;
    }
To make it configurable, too.
    private boolean absolutePath = false;
...
    public boolean resolvesToAbsolutePath() {
        return absolutePath;
    }
    public void setAbsolutePath(boolean absolutePath) {
        this.absolutePath = absolutePath;
    }
The resulting bean definition in our example is added to red5-default.xml and looks is as follows (beware! name/id matters with Red5 bean resolving strategy):
    <bean id="streamFilenameGenerator" class="org.red5.server.stream.DefaultStreamFilenameGenerator">
        <property name="prefix" value="${sfg.prefix}" />
        <property name="absolutePath" value="${sfg.absolutePath}" />
    </bean>
With the configuration inside red5.properties.
# streamFilenameGenerator configuration
sfg.prefix=/tmp/upload/
sfg.absolutePath=true

Test the pimped version of Red5 with some small changes in the oflaDemo.

As with the Red5 DefaultStreamFilenameGenerator the oflaDemo, DemoService is given a configurable field:
    private String fileDirectory = "streams/";
...
    public void setFileDirectory(String fileDirectory) {
        this.fileDirectory = fileDirectory;
    }
The resulting configurable bean definition inside WEB-INF/red5-web.xml:
    <bean id="demoService.service" class="org.red5.demos.oflaDemo.DemoService">
        <property name="fileDirectory" value="${fileDirectory}" />
    </bean>
is configured via WEB-INF/red5-web.properties
#fileDirectory=streams/
fileDirectory=file:/tmp/upload/
Drop the oflaDemo.war into the embedded Tomcat deploy directory.
server:~> cp oflaDemo.war ~red5/red5/webapps
Red5 will "explode" the web archive and start the application. Point your browser to see the oflaDemo in action.

Installing Red5 on a Linux system

Installing Red5 : Open Source Flash Server on an Ubuntu system from the source

Building and installing a Red5 server on a Linux box.

Adding a new user

First we create a new user for the Red5 service.
server:~> adduser red5
and deactivate the interactive shell.
red5:x:10xy:10xy:,,,:/home/red5:/bin/false

Installing a Red5 server on a Linux server.

Building a Red5 installer

To get a grip on Red5 we import the source into an IDE and build the server locally using the current stable release (as of May 2010 this is version 0.9.1).
server:~> svn checkout http://red5.googlecode.com/svn/java/server/tags/0_9_1
server:~> mv 0_9_1 red5-server-0.9.1
server:~> ln -s red5-server-0.9.1 red5-server-0.9.x
Look into the interior of the server, adapt code as needed.
server:~> cd red5-server-0.9.x
server:~> ant dist
Create a
server:~> ant dist-installer
Or download the latest release from http://code.google.com/p/red5/ if the binary distribution is all you need.

Add a Red5 server startup script

Based on the /etc/init.d/skeleton a startup script for the installation above could look like this:
#! /bin/sh
### BEGIN INIT INFO
# Provides:          red5-server
# Required-Start:    $remote_fs $syslog
# Required-Stop:     $remote_fs $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Red5 server script
# Description:       Based on skeleton
### END INIT INFO

# Author: fluffi 
#
# Do NOT "set -e"

# PATH should only include /usr/* if it runs after the mountnfs.sh script
PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="Red5 server"
NAME=red5-server

DAEMON=/home/red5/red5-server-0.9.x/dist/red5.sh
DAEMON_STOP=/home/red5/red5-server-0.9.x/dist/red5-shutdown.sh
DAEMON_ARGS=""
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME

JAVA_HOME=/usr/lib/jvm/java-6-sun/jre
RED5_HOME=/home/red5/red5-server-0.9.x/dist

# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0

# Read configuration variable file if it is present
[ -r /etc/default/$NAME ] && . /etc/default/$NAME

# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh

# Define LSB log_* functions.
# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
. /lib/lsb/init-functions

DAEMON_OPTS="--quiet"
DAEMON_OPTS="--chdir $RED5_HOME --chuid red5"

#
# Function that starts the daemon/service
#
do_start()
{
	# Return
	#   0 if daemon has been started
	#   1 if daemon was already running
	#   2 if daemon could not be started
	start-stop-daemon --start --background --chdir $RED5_HOME --chuid red5 --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
		|| return 1
	start-stop-daemon --start --background --chdir $RED5_HOME --chuid red5 --pidfile $PIDFILE --startas $DAEMON -- \
		$DAEMON_ARGS \
		|| return 2
	# Add code here, if necessary, that waits for the process to be ready
	# to handle requests from services started subsequently which depend
	# on this one.  As a last resort, sleep for some time.
}

#
# Function that stops the daemon/service
#
do_stop()
{
	# Return
	#   0 if daemon has been stopped
	#   1 if daemon was already stopped
	#   2 if daemon could not be stopped
	#   other if a failure occurred
	start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME
	RETVAL="$?"
	[ "$RETVAL" = 2 ] && return 2
	# Wait for children to finish too if this is a daemon that forks
	# and if the daemon is only ever run from this initscript.
	# If the above conditions are not satisfied then add some other code
	# that waits for the process to drop all resources that could be
	# needed by services started subsequently.  A last resort is to
	# sleep for some time.
	start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
	[ "$?" = 2 ] && return 

	start-stop-daemon --start --background --chdir $RED5_HOME --chuid red5 --pidfile $PIDFILE --startas $DAEMON_STOP -- \
	sleep 10

	# Many daemons don't delete their pidfiles when they exit.
	rm -f $PIDFILE
	return "$RETVAL"
}

#
# Function that sends a SIGHUP to the daemon/service
#
do_reload() {
	#
	# If the daemon can reload its configuration without
	# restarting (for example, when it is sent a SIGHUP),
	# then implement that here.
	#
	start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
	return 0
}

case "$1" in
  start)
	[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
	do_start
	case "$?" in
		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
	esac
	;;
  stop)
	[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
	do_stop
	case "$?" in
		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
	esac
	;;
  restart|force-reload)
	#
	# If the "reload" option is implemented then remove the
	# 'force-reload' alias
	#
	log_daemon_msg "Restarting $DESC" "$NAME"
	do_stop
	case "$?" in
	  0|1)
		do_start
		case "$?" in
			0) log_end_msg 0 ;;
			1) log_end_msg 1 ;; # Old process is still running
			*) log_end_msg 1 ;; # Failed to start
		esac
		;;
	  *)
	  	# Failed to stop
		log_end_msg 1
		;;
	esac
	;;
  *)
	#echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
	echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2
	exit 3
	;;
esac

:

Starting the Red5 server manually

With the red5-server startup script the Red5 server should start with uid=red5.
server:~> sudo /etc/init.d/red5-server start
Point your web browser to http://localhost:5080/ to verify the basic installation:

Making Red5 script run at boot time

server:~> update-rc.d red5-server defaults

Spring Security 3.0.0 in an OSGi/Web environment

Pebble goes OSGi with a current version of spring-security...

Lately Spring Security 3.0.0 Released. A good reason to free my pebble workspace from the dust of late 2009. How about using a current spring-security in an OSGi/Web environment like this pebble installation?

Very good news in web.xml everything looks very familiar (if you don't use Acegi...):

<filter>
  <filter-name>springSecurityFilterChain</filter-name>
  <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>  
</filter>
 <filter-mapping>
  <filter-name>springSecurityFilterChain</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

To provide a nonupdateable username/password account e.g. development/demo purposes an authentication manager like this is all you need:

<security:authentication-manager>
  <security:authentication-provider>
    <security:user-service>
      <security:user name="username" password="password"
          authorities="ROLE_BLOG_ADMIN,ROLE_BLOG_OWNER,ROLE_BLOG_PUBLISHER,ROLE_BLOG_CONTRIBUTOR" />
    </security:user-service>
  </security:authentication-provider>
</security:authentication-manager>

<security:http>
 
  <security:form-login
     login-page="/login.action"
     authentication-failure-url="/loginFailed.action"
     default-target-url="/index.jsp" />
  <security:logout logout-url="/j_spring_security_logout" logout-success-url="/" />
</security:http:>

Running Pebble inside an OSGi environment these Require-Bundle lines provide the needed visibilities.

 org.springframework.security.config;bundle-version="[3.0.0,4.0.0)",
 org.springframework.security.core;bundle-version="[3.0.0,4.0.0)",
 org.springframework.security.taglibs;bundle-version="[3.0.0,4.0.0)",
 org.springframework.security.web;bundle-version="[3.0.0,4.0.0)",

Being curious in OpenID/Kerberos authentication things may change...

When continuous builds go mad...

Trap a long running unix build.sh

On a build server far, far away there was a long running build blocking our continuous integration for hours. One step in our build was waiting for infinity... nobody in the team has access to the build server to kill the mad process.

A fortune gave us access to the shell scripts running during the build and deployment process. With a little shell twiddling:

#!/bin/zsh
# ¸.·´¯`·.´¯`·.¸¸.·´¯`·.¸><(((º>
# .¸¸.·´¯`·.¸><(((º>
#

function alarm_handler {
  echo "[$$] handling alarm"
  kill_subshell
  exit 1
}

function kill_subshell {
  echo "[$$] killing subshell...${PIDS:-$!}"
  kill ${PIDS:-$!}
  if [ $? -eq 0 ]
  then
    echo "[$$] longrunning subshell killed"
  else
    echo "[$$] longrunning subshell ${PIDS} *not* killed due to error"
  fi
}

function set_timer {
  SLEEP_TIME=${1:-10}
  if [ ${SLEEP_TIME} -gt 0 ]
  then
    echo "[$$] timer set to ${SLEEP_TIME} seconds"
    (echo -n "[$$]" && sleep ${SLEEP_TIME} && kill -ALRM $$) & PIDS="${PIDS} $!"
    TIMER_PID=$!
  fi
}

function unset_timer {
  kill ${TIMER_PID}
  if [ $? -eq 0 ]
  then
    echo "[$$] timer successfully unset"
  fi
}

echo "[$$] running a timer test"

trap alarm_handler ALRM
set_timer ${1:-15}

# create a long running subshell ;-)
(for i in 5 10 30 ; do sleep ${i} ; echo -n "[$!] " ; date +"%H:%M:%S" ; done) & PIDS="$!"

echo "[$$] waiting for $!..."
wait $!

unset_timer

echo "[$$] finished job in time"
exit 0

...and the continuous build was happily ever after.

Building A SCRUM StoryCards Generator - Roundup

Putting the pieces together with spring-mvc...

In Part I we squeezed raw data out of an Excel sheet containing the SCRUM story cards. With a little bit of groovy this raw data got converted to XML. I the 2nd Part we created the XML needed as input for our PdfGenerator. In Part III we pimped an Apache FOP example and used an XSL-FO style sheet to render the PDF generated by the SCRUM StoryCards Generator.

We ended up with two OSGi services:

  1. pdfGenerator
  2. storyCardBuilder

To include the OSGi services into our web application we ask Spring to handle all that low level stuff and inject the services directly into our controller:

<osgi:reference id="pdfGenerator" interface="de.datenkollektiv.util.fop.PdfGenerator" />
<osgi:reference id="storyCardBuilder" interface="de.datenkollektiv.scrum.cards.StoryCardBuilder" />

We want spring-osgi to deploy the story card generator into the web context path '/scrum'. This is done with a little hint in MANIFEST.MF

Import-Package:
...
 de.datenkollektiv.util.fop,
 de.datenkollektiv.util.poi,
 de.datenkollektiv.scrum.cards,
...
Web-ContextPath: /scrum

Next we configure the basic part of spring-mvc. Let's have a look at the OSGi aware web.xml:

<context-param>
  <param-name>contextClass</param-name>
  <param-value>org.springframework.osgi.web.context.support.OsgiBundleXmlWebApplicationContext</param-value>
</context-param>
<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<servlet>
  <servlet-name>scrum</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <load-on-startup>1</load-on-startup>
</servlet>

One additional context listener is needed to work with session scoped beans.

<listener>
  <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>

Now let's look into the spring-mvc configuration itself: The well known viewResolver and the Apache commons multipartResolver (for the Excel file upload - surprise, surprise ;-)

<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <property name="prefix" value="/WEB-INF/jsp/" />
  <property name="suffix" value=".jsp" />
</bean>

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver" />

The two controllers storyCardsController and fileUploadController are wired with the needed OSGi services and the session data per constructor for OSGi services and data alike.

<bean id="sessionData" class="de.datenkollektiv.scrum.web.SessionData" scope="session">
  <!-- this next element effects the proxying of the surrounding bean -->
  <!-- required to inject the scoped bean into controllers -->
  <aop:scoped-proxy />
</bean>

<bean id="storyCardsController" class="de.datenkollektiv.scrum.web.StoryCardsController">
  <property name="pdfGenerator" ref="pdfGenerator" />
  <property name="sessionData" ref="sessionData" />
</bean>
<bean id="fileUploadController" class="de.datenkollektiv.scrum.web.FileUploadController">
  <property name="storyCardBuilder" ref="storyCardBuilder" />
  <property name="sessionData" ref="sessionData" />
</bean>

Generating the story cards involves two steps. Upload the Excel sheet and generate the XML datastructure using the OSGi service storyCardBuilder is the first step: /upload.form. (Exception handling has been removed for better readability)

@RequestMapping(value = "/upload.form", method = RequestMethod.POST)
public String onSubmit(HttpServletRequest request, byte[] file) throws Exception {

  ExcelTemplate template;
  template = new ExcelTemplate(file);
  List> data = template.getRows(HEADERS);
  String dataAsXml = storyCardBuilder.buildXml(data);
  sessionData.setData(dataAsXml);
  request.setAttribute("downloadAvailable", true);
  request.setAttribute("storyCardCount", data.size());
  return "storycards";
}

@InitBinder
public void initBinder(WebDataBinder webDataBinder) {
  webDataBinder.registerCustomEditor(byte[].class, new ByteArrayMultipartFileEditor());
}

If the uploaded Excel has been successfully transformed into the XML representation we present the user the /storycards.pdf download link where he can download/trigger the generation of the PDF.

@RequestMapping("/storycards.pdf")
public void generateStoryCards(HttpServletRequest request, HttpServletResponse response) throws Exception {

  Resource xsl = new DefaultResourceLoader().getResource("/StoryCard.xsl");
  InputStream inputStream = xsl.getInputStream();
  String data = sessionData.getData();
  byte[] pdf = pdfGenerator.generatePdf(new ByteArrayInputStream(data.getBytes()), inputStream);

  response.setContentType("application/pdf");
  byte[] buf = new byte[1024];
  OutputStream out = null;
  InputStream in = null;
  try {
    out = response.getOutputStream();
    in = new ByteArrayInputStream(pdf);
    int len;
    while ((len = in.read(buf)) < 0) {
      out.write(buf, 0, len);
    }
    in.close();
  } catch (IOException e) {
  } finally {
...

How to customize the generated PDF with the help of a bundle fragment ist coming next...sorry, Moritz ;-)