Tuesday, April 24, 2007

Making a local copy of a YouTube video

I've had a few people ask me this so it is worth putting down in a blog post.

Video on the web has really exploded over the last year thanks to sites like YouTube and MySpace. Many of these sites stream videos in FLV format. FLV has the advantage of playing on any browser and platform that runs Flash - which is most - and you don't have the hassle of having to install lots of different codecs to get the thing to work in Windows Media Player.

So why would you want to make a local copy of an FLV video instead of viewing the video online in your browser?
  • You cannot view the video if you are not connected to the internet
  • If you don't have enough bandwidth the video can be jerky while continually buffering
  • The "full screen" button on most of the sites doesn't work, forcing you to squint at the tiny little video box
So how can you download an FLV video to your PC? A few sites like YouTubia offer services where you can type in a YouTube URL and get back a download link (see this article for an extensive list of downloading service sites), allowing you to save the video locally with a ".flv" file extension. To view a local FLV file you can use a free FLV player like BitComet FLV Player.

If your computer is more than a couple years old, however, I haven't found a standalone FLV player that smoothly plays full screen. The next step, then, is converting an FLV file to another video format that other players can handle efficiently. Again some people have been kind enough to offer free conversion services such as convert.viloader.net and vixy.net.

So what video format works smoothly and keeps the audio and video tracks in sync? wmv is reasonably smooth on newer computers, but the format that works best on all types of hardware that I've tried is avi using the DivX codecs - download the free player codec from here.

After all that effort you can enjoy watching uncopyrighted videos like Steve Ballmer - Developers, Instructions For Clustering or the Chad Vader - Day Shift Manager series in full-screen glory.

Enjoy.

You know you've been web surfing too long when...

http://www.ziff.net/404/404.htm

Monday, April 23, 2007

Using Java instrumentation to remove logging

Bytecode instrumentation is a common technique used to mainpulate an existing compiled class at either compile time or runtime. I'm used to seeing instrumentation used to add functionality to a class - e.g. add event logging, add statistics counts to method calls, add persistence capability to a POJO - but I came across someone who has used instrumentation to remove functionality.

The idea? Remove a functio call to log4j at runtime if that call is below the default logging level. This way the performance penalty to even the isDebugEnabled()/isInfoEnabled()/etc calls are completely removed. This is really, really clever.

The only downside to this approach that I can think of is if you dynamically change your application log levels downwards then you won't see any additional logging because the logging calls have already been removed.

Wednesday, April 11, 2007

Conditionally Defining Spring Beans

I like the Spring Framework. It is a useful toolkit of infrastructure that you would often build the equivalent of for any medium to large size application - and it is well designed and tested.

One feature missing from Spring that I would find handy is the ability to define a bean only if a particular property is defined or set to a particular value. For example:
  • If the property "enablestats" is defined then enable a JamonPerformanceMonitorInterceptor AOP bean to monitor application performance.
  • If the application is running inside Tomcat - determined by the existence of a Tomcat-specific property - then configure a datasource that registers with the Tomcat JTA transaction manager; otherwise if running inside WebLogic configure a datasource that registers with the WebLogic JTA transaction manager.
Of course instead of conditional logic we could split the bean definitions up into multiple files and connect them together for different tasks - e.g. construct one application context for Tomcat that includes an xml file for a Tomcat-specific datasource, and construct another application context that includes an xml file for a WebLogic-specific datasource - but if you only have a small number of different beans then it may not be worth constructing multiple application contexts at build time.

So what options do you have for conditionally defining beans? You could do programatic configuration like my colleague Solomon Duskis has blogged about. However, configuring a Spring-based application through XML is most common.

So given I'm restricting myself to xml configuration I thought I'de try out the Spring 2.0 Extensible XML Authoring API. This API allows you to add your own attributes to bean definitions or allow you to define beans using your own XML syntax. Using this API I got close to what I wanted, with a few limitations.

So without further suspense here is what a conditionally defined bean looks like:


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:condbean="http://robertmaldon.com/springbeans/condbean"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://robertmaldon.com/springbeans/condbean http://robertmaldon.com/springbeans/condbean/condbean.xsd">

<condbean:cond test="${developmentmode}">
<bean id="industryDAO" class="robertmaldon.app.dao.HibernateIndustryDAO">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
</condbean:cond>
</beans>


What the example above does is "if the system property 'developmentmode' is set to some value (not null and not empty) then define the bean named 'industryDAO'".

As per the Extensible XML Authoring API the infrastructure to set up the above is as follows:

step 1) Authoring The Schema

robertmaldon/springbeans/condbean.xsd

<?xml version="1.0" encoding="UTF-8" standalone="no"?>

<xsd:schema xmlns="http://robertmaldon.com/springbeans/condbean"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
targetNamespace="http://robertmaldon.com/springbeans/condbean"
elementFormDefault="qualified"
attributeFormDefault="unqualified">

<xsd:element name="cond">
<xsd:complexType>
<xsd:sequence>
<xsd:any minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="test" type="xsd:string" use="required"/>
</xsd:complexType>
</xsd:element>

</xsd:schema>


step 2) Coding a NamespaceHandler


package robertmaldon.springbeans;

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

public class ConditionalBeanNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
super.registerBeanDefinitionParser("cond", new ConditionalBeanDefinitionParser());
}
}


step 3) Coding a BeanDefinitionParser


package robertmaldon.springbeans;

import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;

public class ConditionalBeanDefinitionParser implements BeanDefinitionParser {

/** Default placeholder prefix: "${" */
public static final String DEFAULT_PLACEHOLDER_PREFIX = "${";

/** Default placeholder suffix: "}" */
public static final String DEFAULT_PLACEHOLDER_SUFFIX = "}";

/**
* Parse the "cond" element and check the mandatory "test" attribute. If
* the system property named by test is null or empty (i.e. not defined)
* then return null, which is the same as not defining the bean.
*/
public BeanDefinition parse(Element element, ParserContext parserContext) {
if (DomUtils.nodeNameEquals(element, "cond")) {
String test = element.getAttribute("test");
if (!StringUtils.isEmpty(getProperty(test))) {
Element beanElement = DomUtils.getChildElementByTagName(element, "bean");
return parseAndRegisterBean(beanElement, parserContext);
}
}

return null;
}

/**
* Get the value of a named system property (it may not be defined).
*
* @param strVal The name of a system property. The property may
* optionally be surrounded in Ant/EL-style brackets. e.g. "${propertyname}"
*
* @return
*/
private String getProperty(String strVal) {
if (StringUtils.isEmpty(strVal)) {
return null;
}
if (strVal.startsWith(DEFAULT_PLACEHOLDER_PREFIX)) {
if(strVal.endsWith(DEFAULT_PLACEHOLDER_SUFFIX)) {
return System.getProperty(
strVal.substring(DEFAULT_PLACEHOLDER_PREFIX.length(),
strVal.length() - DEFAULT_PLACEHOLDER_SUFFIX.length()));
}
}
return System.getProperty(strVal);
}

private BeanDefinition parseAndRegisterBean(Element element, ParserContext parserContext) {
BeanDefinitionParserDelegate delegate = parserContext.getDelegate();
BeanDefinitionHolder holder = delegate.parseBeanDefinitionElement(element);
BeanDefinitionReaderUtils.registerBeanDefinition(holder, parserContext.getRegistry());

return holder.getBeanDefinition();
}
}


step 4) Register the Handler and the Schema

META-INF/spring.handlers

http\://robertmaldon.com/springbeans/condbean=robertmaldon.springbeans.ConditionalBeanNamespaceHandler


META-INF/spring.schemas

http\://robertmaldon.com/springbeans/condbean/condbean.xsd=robertmaldon/springbeans/condbean.xsd

Special note for developing in Tomcat: Spring looks for META-INF/spring.handlers and META-INF/spring.schemas on the classpath. webapp/META-INF is not on the Tomcat classpath, so you need to put these files inside a JAR or (hack warning) in the webapp/WEB-INF/classes/META-INF directory.

Limitations

A common way of configuring an application with property replacement is to use a PropertyPlaceholderConfigurer bean. Unfortunately this is a two-pass process: the first pass parses a bean definition, the second pass does property replacement. The Extensible Authoring XML API only allows you to interact with the first pass, so that means we are limited to things that are defined at bean definiton time, such as system properties.

The spring-beans-2.0.xsd forces you to define a <condbean:cond/> for single beans - you cannot put a <condbean:cond/> block around a group of beans.

The spring-beans-2.0.xsd prevents you from defining two beans with the same name in the same XML file, even if different <condbean:cond/> conditions guarantee only one of the definitions will be in force at any given time.

Future Enhancments