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 inDefaultStreamFilenameGenerator 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 Red5DefaultStreamFilenameGenerator 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/webappsRed5 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 red5and 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.xLook into the interior of the server, adapt code as needed.
server:~> cd red5-server-0.9.x server:~> ant distCreate a
server:~> ant dist-installerOr 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 thered5-server startup script the Red5 server should start with uid=red5.
server:~> sudo /etc/init.d/red5-server startPoint 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:
pdfGeneratorstoryCardBuilder
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 ;-)
