Wednesday, March 28, 2007

How to build completely dynamic example with Spring 2, Scriptlandia, Beanshell, Groovy and JRuby (revisited)

I made small modifications to previously explained example in order to use the custom dynamic language tags from Spring 2 to define dynamic-language-backed beans. Finally, the spring configuration file 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:lang="http://www.springframework.org/schema/lang"
       xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.0.xsd">

  <!-- Creates cat. -->
  <lang:groovy id="cat" script-source="classpath:Cat.groovy">
    <lang:property name="name" value="cat-name"/>
  </lang:groovy>

  <!-- Creates dog. -->
  <lang:jruby id="dog" script-interfaces="Animal" script-source="classpath:Dog.ruby"
              refresh-check-delay="5000"> <!-- switches refreshing on with 5 seconds between checks -->

    <lang:property name="name" value="dog-name"/>
  </lang:jruby>

  <!-- Creates cow. -->
  <lang:bsh id="cow" script-interfaces="Animal" script-source="classpath:Cow.bsh">
    <lang:property name="name" value="cow-name"/>
  </lang:bsh>

  <!-- Creates ant (inline script). -->
  <lang:groovy id="ant">
    <lang:inline-script>
      class Ant implements Animal {
        String name

        void makeSound() {
          println name + ": Shhh..."
        }
      }
    </lang:inline-script>
    <lang:property name="name" value="ant-name" />
  </lang:groovy>

  <!-- Creates animal farm. -->
  <bean id="farm" class="AnimalFarm">
    <property name="animals">
      <list>
        <ref bean="cat"/>
        <ref bean="dog"/>
        <ref bean="cow"/>
        <ref bean="ant"/>
      </list>
    </property>
  </bean>

</beans>



The final version of example is located here.

Monday, March 19, 2007

How to build completely dynamic example with Spring 2, Scriptlandia, Beanshell, Groovy and JRuby

Recently, I found this project: GroovyWorks;.
It tries to use together such things as Java, Groovy, Spring 2 and Struts 2. Because Struts actions are written as Groovy scripts, it is not required to restart web application for each and every change. It is possible because of dynamic nature of Groovy language.

But still, it's not completely dynamic. Small portion of the system is written in Java and for each change in Java you have to recompile and redeploy your web application. Is it possible to make it completely dynamic?

In the following lines I will explain how to build such completely dynamic code. It is standalone application, so Struts 2 is not required. But we still want to have IoC container. Spring 2 fits for our needs, especially with new struts-scripting library, that supports 3 popular languages: Groovy, JRuby and Beanshell.

1. In order to work with scripts inside Spring 2 we have to create Java classes first. Let's create animal farm that consists of animals:


// Animal.java

public interface Animal {

public void makeSound();

}

// AnimalFarm.java

import java.util.List;

public class AnimalFarm {
private List animals;

public AnimalFarm() {
System.out.println("New Animal farm has been created.");
}

public void setAnimals(List animals) {
this.animals = animals;
}

public void wakeUp() {
for(int i=0; i < animals.size(); i++) {
Animal animal = (Animal)animals.get(i);

animal.makeSound();
}
}

}


2. Now, we can implements different animals in different languages:


// Cat.groovy

class Cat implements Animal {

void makeSound() {
println "Meow!"
}

}

# Dog.rb

require "java"

include_class("Animal")

class Dog < Animal
def makeSound
puts "Bark!!!"
end
end

Dog.new

// Cow.bsh

void makeSound() {
System.out.println("Moo...");
}


3. We'll keep all required libraries in the form of maven2 dependencies file.
Because current version of spring (2.0.3) is not compatible with jruby version 0.9.8
(see bug SPR-3255),
we have to keep reference to temporary repository:
http://scriptlandia-repository.googlecode.com/svn/trunk/patches.

4. All required beans are defined inside spring file:


<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<!-- dynamo-test.xml -->

<beans>
<!-- Enables spring-scripting. -->
<bean class="org.springframework.scripting.support.ScriptFactoryPostProcessor"/>

<!-- Creates cat. -->
<bean id="cat" class="org.springframework.scripting.groovy.GroovyScriptFactory">
<constructor-arg value="file:Cat.groovy" />
</bean>

<!-- Creates dog. -->
<bean id="dog" class="org.springframework.scripting.jruby.JRubyScriptFactory">
<constructor-arg value="file:Dog.ruby" />
<constructor-arg value="Animal" />
</bean>

<!-- Creates cow. -->
<bean id="cow" class="org.springframework.scripting.bsh.BshScriptFactory">
<constructor-arg value="file:Cow.bsh" />
<constructor-arg value="Animal" />
</bean>

<!-- Creates animal farm. -->
<bean id="farm" class="AnimalFarm">
<property name="animals">
<list>
<ref bean="cat"/>
<ref bean="dog"/>
<ref bean="cow"/>
</list>
</property>
</bean>
</beans>


5. The trickiest part here is how to avoid compilation of java code, making code
completely dynamic. To achieve it, we use janino library (http://www.janino.net/)
with JavaSourceClassLoader. We load class from source file, retrieve it as array of bytes
and then add this array as a class to our class loader.

To load required classes/libraries to CLASSPATH we use Scriptlandia API (http://scriptlandia.sf.net).
The complete example is represented below:


// dynamo-test.bsh

org.sf.scriptlandia.ScriptlandiaHelper.addMavenDependencies("pom.xml");

import org.sf.scriptlandia.launcher.ScriptlandiaLauncher;
import org.codehaus.janino.*;

import org.sf.scriptlandia.util.*;
import org.codehaus.classworlds.ClassRealm;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;


public class Dynamo {
private ClassRealm classRealm;
private ApplicationContext factory;

public Dynamo(String basedir, String[] classNames, String beansFile) {
ScriptlandiaLauncher launcher = ScriptlandiaLauncher.getInstance();

classRealm = launcher.getMainRealm();

register(basedir, classNames);

factory = new FileSystemXmlApplicationContext(new String[] { basedir + "/" + beansFile });
}

private void register(String basedir, String[] classNames) {
ScriptlandiaLauncher launcher = ScriptlandiaLauncher.getInstance();

ClassLoader sourceClassloader = new JavaSourceClassLoader(
launcher.getClass().getClassLoader(), // parentClassLoader
new File[] { new File(basedir) }, // optionalSourcePath
(String) null, // optionalCharacterEncoding
DebuggingInformation.NONE // debuggingInformation
);

for(int i=0; i < classNames.length; i++) {
loadClass(classNames[i], sourceClassloader);
}
}

/**
* Loads class specified by the name.
*/
private void loadClass(String name, ClassLoader classLoader) {
String[] args = new String[] { name };

Map bytecodes = ReflectionUtil.invokePrivateMethod(
classLoader,
new Object[] { name },
JavaSourceClassLoader.class,
"generateBytecodes",
new Class[] { String.class });

classRealm.addConstituent(name, bytecodes.get(name));
}

public Object getBean(String beanName) {
return factory.getBean(beanName);
}

public static void main(String[] args) throws Exception {
// Java classes that needs to be registered.
String[] classNames = new String[] {"Animal", "AnimalFarm" };
String basedir = System.getProperty("user.dir");

Dynamo dynamo = new Dynamo(basedir, classNames, "dynamo-test.xml");

AnimalFarm animalFarm = dynamo.getBean("farm");

animalFarm.wakeUp();
}

}

Thursday, March 08, 2007

Building GUI frontend for Maven2 archetype plugin (Beanshell, Swing, Scriptlandia)

There are multiple archetypes available for developers:
  • standard Maven 2 distribution contains 5 archetypes: "archetype", "j2ee-simple", "mojo", "quickstart", "site", "webapp";
  • AppFuse 2.0 project (http://appfuse.org) now is rewritten in form of different archetypes (8): "basic-jsf", "basic-spring", "basic-struts", "basic-tapestry", "modular-jsf", "modular-spring", "modular-struts", "modular-tapestry";
  • WebTide site (http://www.webtide.com/resources.jsp) published archetypes for web development (10): "ActiveMQ", "DOJO", "DWR", "JSF", "SiteMesh", "Spring", "SpringJpa", "Struts", "Tapestry", "WebWork";
  • JPA 101 (http://jroller.com/page/cmaki?entry=jpa_maven_2_archetype) archetype ("hibernate-archetype").
How to handle this amount of different types?

I wrote simple front-end with the help of Swing and Beanshell that displays all input parameters for the project (group ID, artifact ID, version and arche-type). After selecting appropriate archetype and clicking on "Create archetype" button, new project will be created in the current directory.

The source for this script is located here: create-archetype.bsh.

This program uses behind the scene Scriptlandia API to execute maven2 tool:

ScriptlandiaHelper.executeMaven(args);

After this work is done, it's easy to create simple starters for different archetype implementations.

For starting standard maven 2 archetypes:

// maven-archetype.bsh

sourceRelative("create-archetype.bsh");

String[] archetypes = {
"archetype", "j2ee-simple", "mojo", "quickstart", "site", "webapp"
};


CreateArchetype frame = new CreateArchetype("WebTide");

frame.setArchetypes(archetypes);
frame.setArchetypeGroupId("org.apache.maven.archetypes");
frame.setArchetypeArtifactIdPrefix("maven-archetype-");
frame.setArchetypeVersion("1.0");

frame.setVisible(true);

For starting AppFuse archetypes:

// appfuse-archetype.bsh

sourceRelative("create-archetype.bsh");

String[] archetypes = {
"basic-jsf", "basic-spring", "basic-struts", "basic-tapestry",
"modular-jsf", "modular-spring", "modular-struts", "modular-tapestry"
};


CreateArchetype frame = new CreateArchetype("Appfuse");

frame.setArchetypes(archetypes);
frame.setArchetypeGroupId("org.appfuse");
frame.setArchetypeArtifactIdPrefix("appfuse-");
frame.setArchetypeVersion("1.0-m3");
frame.setRemoteRepositories("http://static.appfuse.org/repository");

frame.setVisible(true);


For starting WebTide archetypes:

// webtide-archetype.bsh
// Resource: http://www.webtide.com/resources.jsp

sourceRelative("create-archetype.bsh");

String[] archetypes = {
"ActiveMQ", "DOJO", "DWR", "JSF", "SiteMesh", "Spring", "SpringJpa", "Struts", "Tapestry", "WebWork"
};


CreateArchetype frame = new CreateArchetype("WebTide");

frame.setArchetypes(archetypes);
frame.setArchetypeGroupId("com.webtide");
frame.setArchetypeArtifactIdPrefix("maven-archetype-");
frame.setArchetypeVersion("1.0");
frame.setRemoteRepositories("http://scriptlandia-repository.googlecode.com/svn/trunk/tools");

frame.setVisible(true);


For starting Jpa 101 archetype:

// jpa-archetype.bsh

// Resource: http://jroller.com/page/cmaki?entry=jpa_maven_2_archetype

sourceRelative("create-archetype.bsh");

String[] archetypes = {
"hibernate-archetype"
};

CreateArchetype frame = new CreateArchetype("JPA");

frame.setArchetypes(archetypes);
frame.setArchetypeGroupId("com.sourcebeat");
frame.setArchetypeArtifactIdPrefix("jpa-");
frame.setArchetypeVersion("1.0-SNAPSHOT");
frame.setRemoteRepositories("http://scriptlandia-repository.googlecode.com/svn/trunk/tools");

frame.setVisible(true);

Wednesday, March 07, 2007

New version of Scriptlandia: 2.2.0 has been released!

This release includes new features and new supported languages. The full list of
supported languages include:

- Javascript (1.6R4);
- Groovy (1.0);
- Beanshell (2.0b5);
- Jelly (1.0);
- JRuby (0.9.8);
- Jython (2.2b1);
- Pnuts (1.1);
- Jaskell (1.0);
- JScheme (7.2);
- TCL (1.4.0);
- AWK (0.14);
- f3 (?.?);
- Fortress (?.?);
- Scala (2.3.3);
- Janino (2.5.5);
- Scriptella (0.7);
- Velocity (1.4);
- Freemarker (2.3.9);
- Ant (1.7.0);
- Maven (2.0.5).

What is it: Scriptlandia? It's the effort to build environment on top of JVM. The user don't have
to worry how to install or configure libraries for different scripting languages. It will be done
automatically at installation and/or at execution time.

This project is useful for doing fast prototyping in your favorite language without spending time
on installation/configuration (aka CoC). It's also good for building simple command-line tools.

How is it different from, say scripting-for-java project?

1. It's not tied to Java 6 platform. You can use Java 5; it's possible to have this code ready for java 1.4.

2. You can specify dependencies for the language in the form of maven 2 project file. As the result, these dependencies will be downloaded automatically from the server to your local repository.

3. It's easy to build environment in which scripts are aware of each other. It can be done by adding new dependencies (not through ancient CLASSPATH approach).

4. Language gear is available through the file extension. Thanks to jdic project, corresponding
gear will be executed, based on extension.

5. Based on extension, different convenient programs-launchers can be assigned to existing extensions like jar, war etc. As an example, if jar represents micro-edition application, suitable launcher will be started. In another example we can associate jar file with ant script and extend available commands for jar file (now execute command only, see jdk documentation), but everything whatever could be expressed as ant target.

6. New extensions are introduced: .sl (scriptlandia) and .cw (classworld). They are used for starting arbitrary programs with correct dependencies specified.

7. Ant and Maven scripts are first-class citizens: you can interpret them as another scripting languages.

8. Scriptlandia is integrated with Nailgun server. It means that for simple scripts you can keep
JVM in-memory, drastically reducing start-up time for running scripts.