How to compare types in Java

Here’s an overview of the different methods to identify a type or Object in Java.

There are three basic cases

  1. I want to detect if two variables point to Objects of the same type
  2. I want to detect if two types are compatible
  3. I want to detect if a given variable points to an Object of a given type

Check if two variables point to Objects of the same type

The first way is both the most useless and the most cumbersome, so lets’s get it over with fast:

boolean sameType = ob1.getClass() == obj2.getClass();

That was easy enough, but it gets messy as soon as one of the two may be null:

boolean sameType = (obj1 == null ? obj2 == null : obj1.getClass() == obj2.getClass());

Check if two types are compatible

In the following, I will be using the word type as a synonym for Class objects, which can actually be classes, interfaces, enums and annotations. In this section I’ll ignore null. If you can’t even get your types null-safe, you’re screwed anyway.

If you need two types to be identical, just compare them for identity. According to the Java Language Specification, reference types with equal declarations are actually the same Object

boolean sameType = type1 == type2;

If you need type1 to be a subtype of type2 (where subtype means any kind of extends or implements relation

boolean sameType = type2.isAssignableFrom(type1);
// Examples:
assert List.class.isAssignableFrom(ArrayList.class); // Class ArrayList implements Interface List
assert Collection.class.isAssignableFrom(List.class); // Interface List extends Interface Collection
assert AbstractList.class.isAssignableFrom(ArrayList.class); // Class ArrayList extends
                                                             // Class AbstractList

Check if a variable is of a given type

That’s easiest if you know the type at Compile time

boolean typeOrSubType = obj instanceof SomeType

There are two nice side effects of this check:

  1. it includes an implicit null check, there’s no need to separately check for null
  2. it generates a compile-time error if the types are not related:
String obj = // some code that may or may not initialize the String
boolean typeOrSubType = obj instanceof Integer // this generates a compile error, as the compiler
                                               // knows this can never be true

If you only know the type at runtime, the compiler can’t help you with static analysis, but you’re not completely lost.

Class<?> type = // get the type from somewhere. again, we'll assume the type is not null
boolean typeOrSubType = type.isInstance(obj);

Again, Class.isInstance(Object) gives you an implicit null check.

The above methods check whether the object is an instance of the type or a subtype. If you need to check for the exact type (without subtypes), you can use this code

boolean isExactType = obj != null && obj.getClass() == type;

The distinction between a) exact type and b) type or subtype is mostly important when it comes to overriding the equals(Object) method, depending on your interpretation of the equals() contract. This is discussed in Effective Java Item 8: Obey the general contract when overriding equals and has been discussed various times on StackOverflow, most broadly in the thread Overriding equals and hashCode in Java.

Special types

There are some special methods for some special types of types (pun intended).

Here’s how to detect enums:

enum Duck{
  HUEY, DEWEY, LOUIE;
}
assert Duck.class.isEnum(); // detect that a type is an enum
assert Enum.class.isAssignableFrom(Duck.class); // equivalent, but more cumbersome
Object obj = Duck.HUEY; // << you usually don't want to do this, but we need it for the next line
assert obj instanceof Enum;

Here’s how to detect arrays:

assert String[].class.isArray(); // works for Object arrays,
assert int[].class.isArray();    // for primitive arrays
assert int[][].class.isArray();  // and for multidimensional arrays

Note that you can only identify arrays via their types. There’s no legal instanceof check, so for checking a variable you need to check it for null first:

boolean isArray = obj != null && obj.getClass().isArray()

There’s also the Class.getComponentType() method for detecting an Array’s type. You can use that to detect a multidimensional array:

Class<?> componentType = type.getComponentType();
boolean isMultiDimensionalArray = componentType != null && componentType.isArray();

As you can see, this is rather ugly. Actually, code related to arrays is almost always ugly in Java, which is one of the reasons most people prefer to use Collections over arrays.

Here’s how to detect primitive types:

assert int.class.isPrimitive();

Note that there is no legal way to check the type of a primitive variable, as the type of primitive variables is always known to be the variable type. While an Object can actually be a String, and int can never be a long. Both of these generate compiler errors:

assert 1 instanceof int;
assert 1 instanceof Integer;

There’s also no easy way to detect if an Object is of one of the primitive wrapper types. If you have access to the Guava library, you can use Primitives.isWrapperType(Class) for this, though.

Posted in Uncategorized | Tagged , , | Leave a comment

Two Guava Collections

Today I wrote two different Guava Collections as answer to two different StackOverflow Questions:

Java data Structure and
Comparator and equals()

The first requested a Map where the keys are Enums and the values are sorted collections.

In my answer, I implemented a Guava MultiMap that does just that. It uses EnumMap internally if it knows the enum’s type and a HashMap or a user-supplied map otherwise (each of these three Constructors has a sibling with a custom comparator that sorts the values).

The second was apparently based on the first and dealt with the problem that the OP would like to use a SortedSet, but the business objects did not have a valid compareTo() / equals() relation, so I suggested that a sorted List would be better. I implemented such a List using Guava’s ForwardingList as base and redirecting the logic to an internal ArrayList.

Posted in Guava, Java | Leave a comment

Add system properties through spring XML configuration.

If you want to set system properties through XML configuration, here is a solution (inspired by this question):

Use two MethodInvokingFactorybeans, one to statically access System.getProperties() and one to call properties.putAll(map) on these properties. Here’s the configuration:

<bean
    class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    <property
        name="targetObject">
        <!-- System.getProperties() -->
        <bean
            class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
            <property name="targetClass" value="java.lang.System" />
            <property name="targetMethod" value="getProperties" />
        </bean>
    </property>
    <property
        name="targetMethod"
        value="putAll" />
    <property
        name="arguments">
        <!-- The new Properties -->
        <util:properties>
            <prop
                key="my.key">myvalue</prop>
            <prop
                key="my.key2">myvalue2</prop>
            <prop
                key="my.key3">myvalue3</prop>

        </util:properties>
    </property>
</bean>

Of course I am not saying this is a best practice (far from it). But it’s probably the easiest way to add system properties using XML only.

Posted in Java, Spring | 2 Comments

Wicket behavior to guard changed form.

Triggered by the StackOverflow Question Wicket : Notify if page model has been changed., here is a wicket behavior that you can attach to a form and that will stop the user from leaving the site once he has changed some values of the form.

Here is the Javascript file (it requires that either Prototype or MooTools are available):


// initialize namespace
if(!window.wicket)window.wicket={};
if(!wicket.behaviors)wicket.behaviors={};
if(!wicket.behaviors.guardform)wicket.behaviors.guardform={};

// check whether prototype or mootools are installed
wicket.behaviors.guardform.prototypeinstalled = window.Prototype && window.Prototype.version;
wicket.behaviors.guardform.mootoolsinstalled = window.MooTools && window.MooTools.version;
if(wicket.behaviors.guardform.mootoolsinstalled && !window.Selectors){
	throw "MooTools must contain the Selectors api to use this behavior";
}

wicket.behaviors.guardform.dirty = false;

// default prompt, can be adjusted from the wicket behavior
wicket.behaviors.guardform.prompt = "You have changed values in the form.\n"
    + "Do you really want to edit and lose your changes?";

wicket.behaviors.guardform.attach=function(elem,event,handler){
	if(wicket.behaviors.guardform.mootoolsinstalled){
		elem.addEvent(event,handler);
	}else if(wicket.behaviors.guardform.prototypeinstalled){
		elem.observe(event,handler);
	}
	throw "Prototype or MooTools must be present to use this behavior";
}

wicket.behaviors.guardform.select=function(elem,args){
	if(wicket.behaviors.guardform.mootoolsinstalled){
		return elem.getElements(args);
	}else if(wicket.behaviors.guardform.prototypeinstalled){
		return elem.select(args);
	throw "Prototype or MooTools must be present to use this behavior";
}

wicket.behaviors.guardform.parent=function(elem,selector){
	if(wicket.behaviors.guardform.mootoolsinstalled){
		return elem.getParent(args);
	}else if(wicket.behaviors.guardform.prototypeinstalled){
		return elem.up(args);
	throw "Prototype or MooTools must be present to use this behavior";
}

wicket.behaviors.guardform.init=function(id){

	var obj = $(id);
	if(obj){
		var first = true;

		// attach onchange behavior for all child text components
		wicket.behaviors.guardform.select(
				obj,
				"input[type=text], " // text fields
				"textarea, " // text areas
				"select" // select controls
		).each(function(item){
			if(first){
				first=false;
			}
			wicket.behaviors.guardform.attach(item, "change", function(){
				wicket.behaviors.guardform.dirty = true;
			});
		});

		// attach window unload handler that will fire a prompt if form is dirty
		wicket.behaviors.guardform.attach(window, "beforeunload", function(){
			return (!wicket.behaviors.guardform.dirty ||
                                confirm(wicket.behaviors.guardform.prompt);
		});

		// now attach an 'undirty' function to the form
		wicket.behaviors.guardform.attach(
			obj,
			'submit',
			function(){
				wicket.behaviors.guardform.dirty = false;
			}
		);

	}

}

Then you need the actual wicket behavior, that you attach to the wicket form component (This version can only be attached to root forms, not nested forms, as they are internally remodeled to <div> tags). Here’s the Behavior class:

public class GuardDirtyFormBehavior extends AbstractBehavior{

    private Component component;

    private IModel promptModel = null;

    public GuardDirtyFormBehavior setPromptModel(final IModel promptModel){
        this.promptModel = promptModel;
        return this;
    }

    @Override
    public void bind(final Component component){
        if(!(component instanceof Form)){
            throw new WicketRuntimeException("Behavior must be attached to a form");
        }
        // if the model needs component wrapping, wrap it here
        if(promptModel instanceof IComponentAssignedModel<?>){
            promptModel=((IComponentAssignedModel<String>)promptModel).wrapOnAssignment(component);
        }
        this.component = component.setOutputMarkupId(true);
    }

    @Override
    public void onComponentTag(final Component component, final ComponentTag tag){
        if(!"form".equalsIgnoreCase(tag.getName())){
            throw new WicketRuntimeException("Behavior must be attached to a root form, not a nested form.");
        }
    }

    @Override
    public void renderHead(final IHeaderResponse response){
        response.renderJavascriptReference(new JavascriptResourceReference("GuardDirtyFormBehavior.js"));
        if(this.promptModel != null){
            final String prompt = this.promptModel.getObject();
            response.renderJavascript("wicket.behaviors.guardform.prompt='"
                + prompt + "';", "wicket.behaviors.guardform.prompt");
        }
        response.renderOnDomReadyJavascript("wicket.behaviors.guardform.init('"
            + this.component.getMarkupId() + "');");
    }

}

You can assign it like this:

public class Mypage extends WebPage{
    public Mypage(){
        add(
            new Form<Object>("form")
            .add(
                new TextField<String>("textField1"),
                new TextField<String>("textField2"),
                new TextField<String>("textField3"),
                new TextField<String>("textField4")
            )
            .add(new GuardDirtyFormBehavior())
        );
     }
}

You can also change the javascript prompt message using the wicket i18n mechanism by using setPromptModel together with a ResourceModel:

.add(new GuardDirtyFormBehavior()
    .setPromptModel(new ResourceModel("form.dirty.prompt"))
)

This will use the wicket resource loading mechanism to look up a localized version of the resource key ‘form.dirty.prompt’.

Posted in Java, Wicket | Leave a comment

Compile .less files to .css in maven (again using GMaven)

I have recently come across LessCSS, a CSS meta-language that introduces code re-usability. I have been looking for a solution like this for some years now, as I hate having to write the same stuff over and over again. So I immediately created some .less files in a current web project of mine, but when it came to automatically compiling them, things turned out to be less easy.

LessCss was originally written in ruby, so my first approach was to embed jruby in maven and do the compiling like that. However, JRuby is huge and it takes ages to start up, and also there are issues about file paths to the jruby.jar that are not allowed to contain spaces etc. All in all it was just a pain.

Then I had the idea to do the processing at runtime, not compile time, using a PackageResource in wicket. While doing some research for this I came across this blog post, where the author had done exactly this. However, I still believe this kind of pre-processing should be done at compile time, so I was very happy to see that the author uses a slightly different approach using the re-written javascript version of LessCss embedded in Mozilla Rhino. After some hacking on my part this solution works perfectly for me using GMaven. Basically I rewrote the inner workings of the visural less processing in groovy, so my language stack is this:

Java (Maven) ==> Groovy (GMaven) ==> JavaScript (Rhino)

So here is the groovy script that does the work:

import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextFactory;
import org.mozilla.javascript.ScriptableObject;
import org.slf4j.Logger;

public class LessCssJsCompiler {

    private final String lessjs =
        new File("src/main/javascript/less.js").text;
    private final ContextFactory cf;
    private final Context c;
    private final ScriptableObject so;
    private final Logger log

    private final String runjs = """\
    var lessIt = function(css) {
        var result;
        var parser = new less.Parser();

        parser.parse(css, function (e, root) {
            result = root.toCSS();
        });
        return result;
    };
""";

    private File srcRoot;
    private File trgRoot;

    public LessCssJsCompiler(File src, File trg, Logger log){
        this.log = log;
        this.srcRoot=src.getAbsoluteFile();
        log.info "Source folder: ${this.srcRoot}";
        this.trgRoot=trg.getAbsoluteFile();
        log.info "Target folder: ${this.trgRoot}";
        this.cf = new ContextFactory();
        this.c = cf.enterContext();
        this.so = c.initStandardObjects();
        c.setOptimizationLevel(9);
        c.evaluateString(so, lessjs, "less.js", 1, null);
        c.evaluateString(so, runjs, "run.js", 1, null);
        processFolder(this.srcRoot);
    }

    void processFolder(File folder){
        folder.eachFile {
            if(it.isDirectory()){
                if(it.name != 'CVS'){ // or whatever your SCM is
                    processFolder it;
                }
            }else{
                if(it.name.endsWith('.less'))
                    processFile(it, getMirrorFile(it));
            }
        }
    }

    void processFile (File input, File output){
        if(output.exists() && output.lastModified() > input.lastModified()){
            log.info "File $output is up to date, skipping."
        }else{
            log.info "Compiling file $input to $output";
            output.parentFile.mkdirs();
            output.text = less(input.text);
        }
    }

    File getMirrorFile(File input){
        File parentFolder = new File(input.parentFile.absolutePath.
                replace(srcRoot.absolutePath, trgRoot.absolutePath));
        def output = new File(
                parentFolder,
                input.name.replace(".less", ".css")
                );
        log.info "Using output file $output for input file $input";
        return output;
    }

    public String less(String data) {
        String lessitjs = "lessIt(\"" +
               data.replace("\"", "\\\"")
                   .replaceAll( /\s+/, ' ' ) // strip line feeds
                   .replaceAll( /\/\*.*?\*\//, '' )+"\");"; //strip comments
        return this.c.evaluateString(
                         so, lessitjs, "lessitjs.js", 1, null).toString();
    }
}

The relevant part of the POM.xml is this:

inside dependencies:

<dependency>
    <groupId>rhino</groupId>
    <artifactId>js</artifactId>
    <version>1.7R2</version>
    <scope>provided</scope>
</dependency>

(you could also use a plugin dependency, but when I use a project dependency, eclipse will give me autocompletion etc)

inside build/plugins:

<plugin>
 <groupId>org.codehaus.groovy.maven</groupId>
 <artifactId>gmaven-plugin</artifactId>
 <version>1.0</version>
 <executions>
 <execution>
 <id>translate-css</id>
 <phase>process-resources</phase>
 <goals>
 <goal>execute</goal>
 </goals>
 <configuration>
 <scriptpath>
 <element>${pom.basedir}/src/main/groovy</element>
 </scriptpath>
 <source>
 <![CDATA[

 new LessCssJsCompiler(
     new File(pom.basedir, "src/main/java"),
     new File(pom.build.outputDirectory),
     log
 );
 ]]>
 </source>
 </configuration>
 </execution>
 </executions>
 </plugin>

In order for this to work, the less.js script (I am using this version) must be inside src/main/javascript and the groovy script must be inside src/main/groovy.

Also, you probably don’t want the .less files in your target folder, so you should remove them from resource processing

for src/main/resources:

<resource>
     <directory>src/main/resources</directory>
     <excludes>
         <exclude>**/*.less</exclude>
     </excludes>
</resource>

or for src/main/java (which is probably where stuff is if you use wicket):

<resource>
     <directory>src/main/java</directory>
     <excludes>
         <exclude>**/*.java</exclude>
         <exclude>**/*.less</exclude>
     </excludes>
</resource>
Posted in CSS, Groovy, JavaScript, Maven | 10 Comments

Deploy a folder hierarchy to a maven repository

Again, prompted by a Stackoverflow question, here is a means to deploy an entire directory including subdirectories to a maven repository.

I am using GMaven (groovy scripts embedded in maven), a great way to script and interact with maven without having to write plugins.

For a lot of what we’ll do, we need to access components from the underlying plexus container (Maven’s IOC system). Fortunately, GMaven provides several pre-defined variables:

project The maven project, with auto-resolving properties
pom Alias for project
session The executing MavenSession
settings The executing Settings
log A SLF4J Logger instance
ant An AntBuilder instance for easy access to Ant tasks
fail() A helper to throw MojoExecutionException

The way to access the plexus container is to call MavenSession.lookup(role), so you’lll be seeing a lot of code like this:


def repoFactory = session.lookup(
    'org.apache.maven.artifact.repository.ArtifactRepositoryFactory');

(Plexus role ids are usually equivalent to the qualified name of the interface that is returned)

My first Idea was to copy the functionality of the Maven Build-Helper Plugin‘ s attach-artifact mojo: attach artifacts to the current build, deploying or installing them as appropriate in the current maven lifecycle context. However, this mechanism relies on AttachedArtifacts, which must have the same groupId and artifactId as the containing project, which I didn’t think to be appropriate for a folder hierarchy.

Instead, I copied some of the code from the deploy:deploy-file mojo, grabbing repository information from the POM’s <distributionManagement> section.

Here are the plexus components we need:

def layout =  session.lookup('org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout');
def repoFactory = session.lookup(
    'org.apache.maven.artifact.repository.ArtifactRepositoryFactory');
def repository = repoFactory.createDeploymentArtifactRepository(
    pom.distributionManagement.repository.id,
    pom.distributionManagement.repository.url,
    layout, true );
def localRepository = session.localRepository;
def helper =
    session.lookup("org.apache.maven.project.MavenProjectHelper");
def afm = session.lookup(
    'org.apache.maven.artifact.handler.manager.ArtifactHandlerManager');
def factory = new org.apache.maven.artifact.factory.DefaultArtifactFactory();
factory.class.getDeclaredField("artifactHandlerManager").accessible = true;
factory.artifactHandlerManager=afm;
def deployer = session.lookup(
'org.apache.maven.artifact.deployer.ArtifactDeployer');

Then we grab some defaults from the <properties> block:

// initialize properties
def baseFolder = new File(pom.properties['deploy.basefolder']);
def groupId = pom.properties['deploy.groupId'];
def version = pom.properties['deploy.version'];

Now we iterate over all files, create an artifact and a temporary pom for each and deploy these using the Deployer component.


// iterate over all files recursively
baseFolder.eachFileRecurse{
    if(it.isDirectory())return;

    // use the file extension as packaging
    def packaging = it.name.replaceAll( /.+\./ , '' );
    // create the artifactId from the file's relative path
    def artifactId = it.absolutePath
        .replace(baseFolder.absolutePath, '')
        .substring(1)
        .replaceFirst( /\..*?$/ , '')
        .replaceAll( /\W+/ , '-' );

    // build a temporary artifact
    def artifact =
        factory.createBuildArtifact(
            groupId, artifactId, version, packaging );

    // create a pom file for the artifact
    def model = new org.apache.maven.model.Model();
    model.setModelVersion( "4.0.0" );
    model.setGroupId( groupId );
    model.setArtifactId( artifactId );
    model.setVersion( version );
    model.setPackaging( packaging );
    File pomFile = File.createTempFile( "mvndeploy", ".pom" );
    pomFile.deleteOnExit();
    fw = org.codehaus.plexus.util.WriterFactory.newXmlWriter( pomFile );
    new org.apache.maven.model.io.xpp3.MavenXpp3Writer().write( fw, model );
    org.apache.commons.io.IOUtils.closeQuietly( fw );

    def metadata =
        new org.apache.maven.project.artifact.ProjectArtifactMetadata(
                    artifact, pomFile );
    artifact.addMetadata( metadata );

    // deploy file
    deployer.deploy(it, artifact, repository, localRepository );
}

And here is the entire pom.xml:

<project
    xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.mycompany</groupId>
    <artifactId>folderdeployer</artifactId>
    <packaging>jar</packaging>
    <version>SNAPSHOT</version>

    <properties>
        <!-- This is where your artifacts are -->
        <deploy.basefolder>c:\temp\stuff</deploy.basefolder>

        <!-- This will be used as groupId -->
        <deploy.groupId>my.groupid</deploy.groupId>

        <!-- this will be used as version -->
        <deploy.version>1.2.3</deploy.version>
    </properties>
    <build>
        <plugins>

            <plugin>
                <groupId>org.codehaus.groovy.maven</groupId>
                <artifactId>gmaven-plugin</artifactId>
                <version>1.0</version>
                <dependencies>
                    <dependency>
                        <groupId>commons-io</groupId>
                        <artifactId>commons-io</artifactId>
                        <version>1.4</version>
                    </dependency>
                </dependencies>
                <executions>
                    <execution>
                        <id>deploy-files</id>
                        <phase>prepare-package</phase>
                        <goals>
                            <goal>execute</goal>
                        </goals>
                        <configuration>
                            <source>
                            <![CDATA[
// read components from plexus container
def layout = session.lookup(
    'org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout');
def repoFactory = session.lookup(
    'org.apache.maven.artifact.repository.ArtifactRepositoryFactory');
def repository = repoFactory.createDeploymentArtifactRepository(
    pom.distributionManagement.repository.id,
    pom.distributionManagement.repository.url,
    layout, true );
def localRepository = session.localRepository;
def helper =
    session.lookup("org.apache.maven.project.MavenProjectHelper");
def afm = session.lookup(
    'org.apache.maven.artifact.handler.manager.ArtifactHandlerManager');
def factory = new org.apache.maven.artifact.factory.DefaultArtifactFactory();
factory.class.getDeclaredField("artifactHandlerManager").accessible = true;
factory.artifactHandlerManager=afm;

def deployer = session.lookup(
    'org.apache.maven.artifact.deployer.ArtifactDeployer');

// initialize properties
def baseFolder = new File(pom.properties['deploy.basefolder']);
def groupId = pom.properties['deploy.groupId'];
def version = pom.properties['deploy.version'];

// iterate over all files recursively
baseFolder.eachFileRecurse{
    if(it.isDirectory())return;

    // packaging = file.extension
    def packaging = it.name.replaceAll( /.+\./ , '' );
    // artifactId = file.relativePath.replace '/' , '-'
    def artifactId = it.absolutePath
        .replace(baseFolder.absolutePath, '')
        .substring(1)
        .replaceFirst( /\..*?$/ , '')
        .replaceAll( /\W+/ , '-' );
    def artifact =
        factory.createBuildArtifact(
            groupId, artifactId, version, packaging );

    // create pom for artifact
    def model = new org.apache.maven.model.Model();
    model.setModelVersion( "4.0.0" );
    model.setGroupId( groupId );
    model.setArtifactId( artifactId );
    model.setVersion( version );
    model.setPackaging( packaging );
    File pomFile = File.createTempFile( "mvndeploy", ".pom" );
    pomFile.deleteOnExit();
    fw = org.codehaus.plexus.util.WriterFactory.newXmlWriter( pomFile );
    new org.apache.maven.model.io.xpp3.MavenXpp3Writer().write( fw, model );
    org.apache.commons.io.IOUtils.closeQuietly( fw );

    def metadata =
        new org.apache.maven.project.artifact.ProjectArtifactMetadata(
                    artifact, pomFile );
    artifact.addMetadata( metadata );

    // deploy file
    deployer.deploy(it, artifact, repository, localRepository );
}
                                    ]]>
                            </source>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

    <distributionManagement>
        <repository>
            <id>your repo id here</id>
            <url>scp://your.repo.url/here</url>
            <layout>default</layout>
        </repository>
    </distributionManagement>

</project>

Put it anywhere you want, edit the <properties> and the <distributionManagement> section and call it using

mvn package
Posted in Groovy, Maven | 1 Comment

Using Prototype / Ajax in Hudson

Triggered by the stackoverflow question Hudson: Running all jobs in a view by pressing one button, I played a bit with Ajax on Hudson using the Hudson API. The nice thing is: Hudson ships with the prototype api (but unfortunately with an ancient version, 1.5.x ), so that makes it easy to develop either quick inline scripts or bookmarklets.

The problem in the question was to create a button or link that triggers all jobs in a build with one click. Now there are several ways to do this using javascript / ajax:

  1. Use a browser bookmarklet
  2. Edit the site description

Generally, what we’ll do is work with the view’s api from within a view. So if your view has the URL http://myhudson/view/myview, the api is at http://myhudson/view/myview/api (try it)

What we’ll use is a script that queries the current view for it’s jobs and for each of those jobs, sends an Ajax get request to <job url>/build to trigger a new build. (Consult a job’s api by looking up http://myhudson/job/myjobname/api ).

So the basic script is this:

<script type="text/javascript">
<!--
function triggerBuilds(obj){
    obj.responseText.evalJSON()['jobs'].each(
            function(i){
                new Ajax.Request(i['url']+'build',{method:'GET'});
            }
    );
}

function buildAll(){
    new Ajax.Request(
            document.URL.replace(/[\W]+$/,'') + '/api/json',
            {
                onSuccess : triggerBuilds,
                method : 'GET'
            }
    );
}

Now we can either compress it and use it in a bookmarklet like this one:
javascript:var%20f%3dfunction(obj)%7bobj.responseText.evalJSON()%5b%27jobs%27%5d.each(function(i)%7bnew%20Ajax.Request(i%5b%27url%27%5d%2b%27build%27%2c%7bmethod%3a%27GET%27%7d)%3b%7d)%3b%7d%3bnew%20Ajax.Request(document.URL.replace(%2f%5b%5cW%5d%2b%24%2f%2c%27%27)%2b%27%2fapi%2fjson%27%2c%7bonSuccess%3af%2cmethod%3a%27GET%27%7d)%3bvoid(0)
(Triple-click the code to select it, copy it to the clipboard and create a new bookmark with this URL. Unfortunately I can’t provide the link here, due to restrictive javascript settings on wordpress)

Or edit the view’s description and paste the full code inside (including a link that calls the function):

<script type="text/javascript">
<!--
function triggerBuilds(obj){
    obj.responseText.evalJSON()['jobs'].each(
            function(i){
                new Ajax.Request(i['url']+'build',{method:'GET'});
            }
    );
}

function buildAll(){
    new Ajax.Request(
            document.URL.replace(/[\W]+$/,'') + '/api/json',
            {
                onSuccess : triggerBuilds,
                method : 'GET'
            }
    );
}

//-->
</script>
<a href="javascript:buildAll();void(0)">Build all Jobs in this view</a>

Another way to do it is to add links to all view tabs from the main page. Edit the main page description and post this code:

<style type="text/css">
a.buildLink {
display:inline-block !important;
text-decoration:none;
color:black;
}</style>
<script type="text/javascript">
<!--
function triggerBuilds(obj){
 obj.responseText.evalJSON()['jobs'].each(
 function(i){
 new Ajax.Request(i['url']+'build',{method:'GET'});
 }
 );
}

function buildAll(url){
 new Ajax.Request(
 (url?url:document.URL).replace(/[\W]+$/,'') + '/api/json',
 {
 onSuccess : triggerBuilds,
 method : 'GET'
 }
 );
}
function addlinks(){
$$('#viewList a').filter(function(item){return item.href.indexOf('/view/')>0;}).each(function(item){
item.setAttribute("style","display:inline");
new Insertion.After(item,
  '<a href="javascript:buildAll(\'' + item.href + '\');void(0)" title="Build all jobs in this view">[Build]</a>');
});
$$('#viewList .active').each(function(item){
new Insertion.Bottom(item,'<a href="javascript:buildAll();void(0)" title="Build all jobs">[Build]</a>');
});
}

Event.observe(window,'load', addlinks);
//-->
</script>

Now all view tabs on the main page will contain a [build] link that will launch all builds of this view.

Posted in Hudson, JavaScript | Leave a comment