Montezuma theme: Colorbox CSS for max width

The Montezuma theme for WordPress is a great theme. It comes with lots of additional stuff you otherwise would need a plugin for, such as the jQuery colorbox overlay for photo slideshows. Unfortunately, by default the size of the overlay will be the exact same size as the picture. So when the pictures aren’t uploaded in 800×600, you can’t really see something. Fortunately Montezuma lets you edit the CSS inline, and this is what you need to add in gallery.css:

max-width: 800px; max-height: 800px;

#cboxPrevious, #cboxNext, #cboxClose, #cboxSlideshow{cursor:pointer;}
.cboxPhoto{float:left; margin:auto; border:0; display:block; max-width:800px; max-height:800px;}
.cboxIframe{width:100%; height:100%; display:block; border:0;}

Jackson: Serialize Map with non-String key (in fact with any Serializable key) and abstract classes

I recently stumbled over the otherwise really great Jackson JSON parser’s inability to (de)serialize a Map that has a non-String key out of the box. The explanation is rather self-evident. JSON is a String-based format and thus any key for a Map has to be a String. So if you don’t specify a custom serializer/deserializer like here, Jackson just calls toString() on your key on serialization. Consider the following simple example:

class Container
{
    @JsonProperty( "map" )
    Map<Key, String> map = new HashMap<Key, String>();
}

class Key
{
    String compositeIdString;
    double compositeIdDouble;
}

Container container = new Container();
Key key = new Key();
key.compositeIdString = "test";
key.compositeIdDouble = 100;                   
container.map.put( key, "test1" );
// serialize json object from container with Map
ObjectMapper mapper = new ObjectMapper();
String outputJson = mapper.writeValueAsString( container );
System.out.println(outputJson);
Container deserializedContainer =
    mapper.readValue( outputJson, Container.class );
assertEquals( container, deserializedContainer );

The result is rather sad:

{“map”:{“Key@8a353ed”:”test1″}}

But it makes sense. Imagine what happens upon deserialization: your key class needs to be re-created from the output of toString(). So adding a proper toString() method and implementing a deserialzier like from this post solves the issue here:

class KeyKeyDeserializer extends KeyDeserializer
{
    @Override
    public Object deserializeKey( String key,
        DeserializationContext ctxt )
        throws IOException, JsonProcessingException
    {
        Key newKey = new Key();
        newKey.compositeIdString = parseString( key );
        newKey.compositeIdString = parseDouble( key );
        return newKey;
    }

    double parseDouble(String key) { ... }

    String parseString(String key) { ... }
}

// annotate our map with the new deserializer
class Container
{
    @JsonDeserialize( keyUsing = KeyKeyDeserializer.class )
    Map<Key, String> map = new HashMap<Key, String>();
}

Still, this is kind of awkward. You need to parse your Key class from the String. And there is one far more important use case here: Using abstract classes as keys. Although Jackson can deal with abstract classes just fine, it cannot properly deserialize the key on its own. Lets consider another example:

class ContainerAbstract
{
    @JsonDeserialize( keyUsing = KeyKeyDeserializer.class )
    Map<AbstractKey, String> map =
        new HashMap<AbstractKey, String>();
}

abstract class AbstractKey
{ 
}
    
class Key1 extends AbstractKey
{ 
    String compositeIdString;    
}
class Key2 extends AbstractKey
{
    double compositeIdDouble;
}

Lets just assume that in real life AbstractKey would make a little more sense than in the above example and you cannot infer the actual type of the abstract class at runtime by specifying a list of possible types or some attribute guessing logic, this is what happened to me. The Key class was an abstract type that was meant to be overridden by users of the framework, and there were already hundreds of concrete Key classes cluttered across a huge code base.
So the problem now is that you can no longer instantiate your abstract class in the deserializer. You don’t know the actual type and you cannot guess it either. And the type information coming in is just a String.

Then it struck me: Jackson was successfully serializing and deserializing the AbstractKey class at another position in the code, where it was just referenced by itself, not contained in a map. It just converted it to JSON, adding enough information to re-create it using reflection at runtime. So  why not using the JSON String (sic!) as the key value? Then we basically have JSON within JSON, and using Jackson to de-serialize our key. Here is what it looks like in code:

class SerializableKeySerializer
    extends JsonSerializer<AbstractKey>
{
    static ObjectMapper mapper = new ObjectMapper();

    @Override
    public void serialize( AbstractKey value,
        JsonGenerator jgen,
        SerializerProvider provider )
        throws IOException, JsonProcessingException
    {
        String json = mapper.writeValueAsString( value );
        jgen.writeFieldName( json );
    }
}

class SerializableKeyDeserializer extends KeyDeserializer
{
    static ObjectMapper mapper = new ObjectMapper();

    @Override
    public Object deserializeKey( String key,
        DeserializationContext ctxt )
        throws IOException, JsonProcessingException
    {
        return mapper.readValue( key, AbstractKey.class );
    }
}

@JsonTypeInfo(use=Id.CLASS,
    include = JsonTypeInfo.As.PROPERTY, property = "c")
class Container
{       
    @JsonSerialize( keyUsing =
         SerializableKeySerializer.class )
    @JsonDeserialize( keyUsing =
         SerializableKeyDeserializer .class ) 
    Map<AbstractKey, String> map =
         new HashMap<AbstractKey, String>();
}

Container container = new Container();
Key1 key1 = new Key1();
key1.compositeIdString = "test";
Key2 key2 = new Key2();
key2.compositeIdDouble = 100;      
container.map.put( key1, "test1" );
container.map.put( key2, "test2" );

// serialize
ObjectMapper mapper = new ObjectMapper();
String outputJson = mapper.writeValueAsString( container );
System.out.println(outputJson);

// de-serialize container from stream
Container deserializedContainer =
    mapper.readValue( outputJson, Container.class );
assertEquals( container, deserializedContainer );

And this is the JSON output when combined with our two Key1 and Key2 classes and the test code:

{
    "map": {
        "{\"c\":\".Key1\",\"compositeIdString\":\"test\"}":
            "test1",
        "{\"c\":\".Key2\",\"compositeIdDouble\":100}":
            "test2"
    }
}

So we added two annotations for serializer and deserializer for Container, there is also a @JsonTypeInfo annotation at class level. This will create a new attribute ‘c’ which will hold the full qualified class name. You can use any of the other runtime class-resolve methods here. Both the key classes can be deserialized using the json that is in there — technically it is just a string for Jackson. It is escaped so it won’t be interpreted as an additional JSON node. One could start optimizing here, using some kind of compression and/or feed it to a Base64 encoder. This will make it look more like a real key-value pair. But technically, the JSON is sufficient as a key.

Java wrap a properly typed list of subtypes

If you want to create a List or Collection in Java that uses a subclass of a type YourType and it is safe to cast, you can use the following snippet to wrap the objects into the right list:

@SuppressWarnings( "unchecked" )
protected <T> List<T> wrapList( Collection<? extends YourObject> collection )
{
    List<T> result = Lists.newArrayList();
    for ( YourObject state : collection )
    {
        result.add( (T) state );
    }
    return result;
}

GPG data stream encryption and signing with Bouncycastle

I found it incredibly difficult to use BouncyCastle’s Java library to create data that GPG was actually able to decrypt. Even if compression is not the matter because you only have small tokens, it did not work without a Zip-DataGenerator in between (although there are plaintext variants of these Generators among the BC classes). GPG was complaining about not being able to decode character sequence “2D” or something.

You can save the string to a file if you need this, I had to create string data for passing it to a restful API.

protected final String publickeyFile; // the public key of the receiver, in armored format
protected final String privatekeyFile; // your private key ring file 
protected final String userId; // usually your email-address in your key ring
protected final String password; // password to your private key ring
protected final SecureRandom randomGenerator;

/**
 * Encrypt plaintext message using public key from publickeyFile.
 * 
 * @param message the message
 * @return the string
 */
private String encrypt( String message )
    throws PGPException, IOException, NoSuchProviderException
{
    // read public key file, this is an armored key file, no binary stream
    InputStream in =
        new ArmoredInputStream( Thread.currentThread().getContextClassLoader()
            .getResourceAsStream( publickeyFile ) );
    // find the PGP key in the file
    PGPPublicKey publicKey = findPublicGPGKey( in );
    Preconditions
        .checkNotNull( publicKey, Format.format( "Did not find the GPG key in keyfile ", publickeyFile ) );

    // Encode the string into bytes using utf-8
    byte[] utf8Bytes = message.getBytes( UTF8_ENCODING );

    ByteArrayOutputStream compressedOutput = new ByteArrayOutputStream();
    // compress bytes with zip
    PGPLiteralDataGenerator literalDataGenerator = new PGPLiteralDataGenerator();
    // the reason why we compress here is GPG not being able to decrypt our message input but if we do not compress.
    // I guess pkzip compression also encodes only to GPG-friendly characters.
    PGPCompressedDataGenerator compressedDataGenerator =
        new PGPCompressedDataGenerator( CompressionAlgorithmTags.ZIP );
    try
    {
        OutputStream literalDataOutput =
            literalDataGenerator.open( compressedOutput, PGPLiteralData.BINARY, FAKE_PGP_INPUT_FILENAME,
                utf8Bytes.length, new Date() );
        // update bytes in the stream
        literalDataOutput.write( utf8Bytes );
    }
    catch ( IOException e )
    {
        // catch but close the streams in finally
        throw e;
    }
    finally
    {
        compressedDataGenerator.close();
        Closeables.closeQuietly( compressedOutput );
    }

    // now we have zip-compressed bytes
    byte[] compressedBytes = compressedOutput.toByteArray();

    PGPEncryptedDataGenerator encryptedDataGenerator =
        new PGPEncryptedDataGenerator( PGPEncryptedData.CAST5, true, randomGenerator, BC_PGP_PROVIDER );
    // use public key to encrypt data
    encryptedDataGenerator.addMethod( publicKey );

    // literalDataOutput --> compressedOutput --> ArmoredOutputStream --> ByteArrayOutputStream
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    ArmoredOutputStream armoredOut = new ArmoredOutputStream( byteArrayOutputStream );
    OutputStream encryptedOutput = null;
    try
    {
        encryptedOutput = encryptedDataGenerator.open( armoredOut, compressedBytes.length );
        encryptedOutput.write( compressedBytes );
    }
    catch ( IOException e )
    {
        throw e;
    }
    catch ( PGPException e )
    {
        throw e;
    }
    finally
    {
        Closeables.closeQuietly( encryptedOutput );
        Closeables.closeQuietly( armoredOut );
    }
    String encrypted = new String( byteArrayOutputStream.toByteArray() );
    LOG.debug( Format.format( "Message: {} ", message ) );
    LOG.debug( Format.format( "Encrypted: {} ", encrypted ) );
    return encrypted;
}

/**
 * Find public gpg key in InputStream.
 * 
 * @param inputStream the input stream
 * @return the PGP public key
 */
private PGPPublicKey findPublicGPGKey( InputStream inputStream )
    throws IOException, PGPException
{
    // get all key rings in the input stream
    PGPPublicKeyRingCollection publicKeyRingCollection =
        new PGPPublicKeyRingCollection( PGPUtil.getDecoderStream( inputStream ) );
    LOG.debug( Format.format( "key ring size: {}", publicKeyRingCollection.size() ) );
    Iterator<PGPPublicKeyRing> keyRingIter = publicKeyRingCollection.getKeyRings();
    // iterate over keyrings
    while ( keyRingIter.hasNext() )
    {
        PGPPublicKeyRing keyRing = keyRingIter.next();
        Iterator<PGPPublicKey> keyIter = keyRing.getPublicKeys();
        // iterate over public keys in the key ring
        while ( keyIter.hasNext() )
        {
            PGPPublicKey tmpKey = keyIter.next();
            Preconditions.checkArgument( tmpKey != null );
            LOG.debug( Format.format( "Encryption key = {}, Master key = {}, UserIDs: {}",
                tmpKey.isEncryptionKey(), tmpKey.isMasterKey(), Iterators.toString( tmpKey.getUserIDs() ) ) );
            // we need a master encryption key
            if ( tmpKey.isEncryptionKey() && tmpKey.isMasterKey() )
            {
                return tmpKey;
            }
        }
    }
    throw new PGPException( "No public key found!" );
}

/**
 * Find private gpg key in InputStream.
 * 
 * @param inputStream the input stream
 * @param userId the user id
 * @return the PGP secret key
 */
private PGPSecretKey findPrivateGPGKey( InputStream inputStream, String userId )
    throws IOException, PGPException, NoSuchProviderException
{
    // iterate over every private key in the key ring
    PGPSecretKeyRingCollection secretKeyRings =
        new PGPSecretKeyRingCollection( PGPUtil.getDecoderStream( inputStream ) );
    // look for the key ring that is used to authenticate our reporting facilities
    Iterator<PGPSecretKeyRing> privateKeys = secretKeyRings.getKeyRings( userId );
    // iterate over every private key in the ring
    while ( privateKeys.hasNext() )
    {
        PGPSecretKeyRing secretKeyRing = privateKeys.next();
        PGPSecretKey tmpKey = secretKeyRing.getSecretKey();
        Preconditions.checkArgument( tmpKey != null );
        LOG.debug( Format.format( "Signing key = {}, Master key = {}, UserId = {}", tmpKey.isSigningKey(),
            tmpKey.isMasterKey(), userId ) );
        // we want the signing master key
        if ( tmpKey.isSigningKey() && tmpKey.isMasterKey() )
        {
            return tmpKey;
        }
    }
    throw new PGPException( "No private key found!" );
}

/**
 * Sign a plaintext message using our private PGP key file.
 * 
 * @param message the message
 * @return the string
 */
private String sign( String message )
    throws SignatureException, NoSuchAlgorithmException, PGPException, NoSuchProviderException, IOException
{
    InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream( privatekeyFile );
    PGPSecretKey secretKey = findPrivateGPGKey( in, userId );
    PGPPrivateKey privateKey = secretKey.extractPrivateKey( password.toCharArray(), BC_PGP_PROVIDER );

    PGPSignatureGenerator signature =
        new PGPSignatureGenerator( secretKey.getPublicKey().getAlgorithm(), PGPUtil.SHA1, BC_PGP_PROVIDER );
    signature.initSign( PGPSignature.BINARY_DOCUMENT, privateKey );

    Iterator<String> userIds = secretKey.getPublicKey().getUserIDs();
    // use the first userId
    if ( userIds.hasNext() )
    {
        PGPSignatureSubpacketGenerator subpacketGenerator = new PGPSignatureSubpacketGenerator();
        subpacketGenerator.setSignerUserID( false, userIds.next() );
        signature.setHashedSubpackets( subpacketGenerator.generate() );
    }
    else
    {
        throw new ReportingException( "Did not find userId" );
    }

    byte[] utf8Bytes = message.getBytes( UTF8_ENCODING );

    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    ArmoredOutputStream armoredOut = new ArmoredOutputStream( byteArrayOutputStream );

    PGPCompressedDataGenerator compressedDataGenerator = new PGPCompressedDataGenerator( PGPCompressedData.ZLIB );
    BCPGOutputStream bcOutputStream = new BCPGOutputStream( compressedDataGenerator.open( armoredOut ) );

    signature.generateOnePassVersion( false ).encode( bcOutputStream );

    // literalDataOutput --> bcOutputStream--> compressedOutput --> ArmoredOutputStream --> ByteArrayOutputStream
    // &&
    // signatureOutput --> bcOutputStream--> compressedOutput --> ArmoredOutputStream --> ByteArrayOutputStream
    PGPLiteralDataGenerator literalDataGenerator = new PGPLiteralDataGenerator();
    try
    {
        OutputStream literalDataOutput =
            literalDataGenerator.open( bcOutputStream, PGPLiteralData.BINARY, FAKE_PGP_INPUT_FILENAME,
                utf8Bytes.length, new Date() );

        // update bytes in the streams
        literalDataOutput.write( utf8Bytes );
        signature.update( utf8Bytes );

        // close generators and update signature
        literalDataGenerator.close();
        signature.generate().encode( bcOutputStream );
        compressedDataGenerator.close();
    }
    catch ( IOException e )
    {
        throw e;
    }
    catch ( PGPException e )
    {
        throw e;
    }
    finally
    {
        Closeables.closeQuietly( byteArrayOutputStream );
        Closeables.closeQuietly( armoredOut );
    }
    // }
    String signed = new String( byteArrayOutputStream.toByteArray() );
    LOG.debug( Format.format( "Message: {} ", message ) );
    LOG.debug( Format.format( "Signature: {} ", signed ) );
    return signed;
}

Apache httpcomponents-core NIO SSL example broken

The example snippet of a SSL enabled Apache http-components-core-based Webserver did not work out for me. It crashed after every Request with the following message:

Connection closed: [null]
I/O error: I/O dispatch worker terminated abnormally
Shutdown

Printing the StackTrace revealed the following:

org.apache.http.nio.reactor.IOReactorException: I/O dispatch worker terminated abnormally
at org.apache.http.impl.nio.reactor.AbstractMultiworkerIOReactor .execute(AbstractMultiworkerIOReactor.java:324)
at com.demandware.tenantssl.NHttpSSLServer.main(SslPaymentServer.java:176)
Caused by: java.lang.UnsupportedOperationException: Entity template does not implement getContent()
at org.apache.http.entity.EntityTemplate.getContent(EntityTemplate.java:57)
at org.apache.http.nio.entity.NHttpEntityWrapper.(NHttpEntityWrapper.java:56)
at org.apache.http.nio.protocol.AsyncNHttpServiceHandler .sendResponse(AsyncNHttpServiceHandler.java:495)
at org.apache.http.nio.protocol.AsyncNHttpServiceHandler .responseReady(AsyncNHttpServiceHandler.java:362)
at org.apache.http.nio.protocol.BufferingHttpServiceHandler .responseReady(BufferingHttpServiceHandler.java:135)
at org.apache.http.impl.nio.DefaultNHttpServerConnection .produceOutput(DefaultNHttpServerConnection.java:221)
at org.apache.http.impl.nio.ssl.SSLServerIOEventDispatch .outputReady(SSLServerIOEventDispatch.java:252)
at org.apache.http.impl.nio.reactor.BaseIOReactor .writable(BaseIOReactor.java:185)
at org.apache.http.impl.nio.reactor.AbstractIOReactor .processEvent(AbstractIOReactor.java:338)
at org.apache.http.impl.nio.reactor.AbstractIOReactor .processEvents(AbstractIOReactor.java:315)
at org.apache.http.impl.nio.reactor.AbstractIOReactor .execute(AbstractIOReactor.java:275)
at org.apache.http.impl.nio.reactor.BaseIOReactor .execute(BaseIOReactor.java:104)
at org.apache.http.impl.nio.reactor.AbstractMultiworkerIOReactor$Worker .run(AbstractMultiworkerIOReactor.java:542)
at java.lang.Thread.run(Thread.java:662)

As inline subclassed EntityTemplate does not implement getContent(), you just have to add this tiny snippet in the two inline definitions of EntityTemplate for a minimal working SSL-server:

EntityTemplate body = new EntityTemplate(new ContentProducer()
{

	public void writeTo(final OutputStream outstream) throws IOException
	{
		OutputStreamWriter writer = new OutputStreamWriter(outstream, "UTF-8");
		writer.write("<html><body><h1>");
		writer.write("File ");
		writer.write(file.getPath());
		writer.write(" not found");
		writer.write("</h1></body></html>");
		writer.flush();
	}
}) {
	@Override
	public InputStream getContent() {
		return new InputStream() {

			@Override
			public int read() throws IOException
			{
				return 0;
			}

		};
	}
};

Hadoop MultipleOutputCollector MRunit Testcase

Code-Snippet for a test case with MRUnit and Mockito to test a Hadoop Reducer with MultipleOutputs. MRUnit ReduceDriver unfortunately does not return result data when using multiple Ouptuts.

private HashMap<String, MockOutputCollector> mockOutputCollectors = new HashMap<String, MockOutputCollector>();

@Test
public void test() {
	driver.withInput(new Text(INPUT_KEY, INPUT).run();
	List<Type> resultList = new ArrayList<Type>();

	// This has to be done that complicated way as the above driver.run() only returns an empty list
	// if run with ReduceDriver. For some reason it works with MapDriver, though...
	for (String collectorDirectory : mockOutputCollectors.keySet()) {
		MockOutputCollector<NullWritable, Object> mockOutputCollector = mockOutputCollectors
				.get(collectorDirectory);
		for (Pair<NullWritable, Object> pair : mockOutputCollector.getOutputs()) {
			if (pair.getSecond() instanceof MultipleOutputType1) {
				MultipleOutputType1 multipleOutputType = (MultipleOutputType1) pair.getSecond();
				logger.debug("MultipleOutputType1: "
						+ multipleOutputType.toString());
			}
			if  (pair.getSecond() instanceof MultipleOutputType2) {
				[...]
			}

		}
	}
}

@Before
public void setup() {
	for (Directories directory : Directories.values()) {
		mockOutputCollectors.put(directory.getDirectoryName(),
				new MockOutputCollector());
		when(
				multipleOutputs.getCollector(
						eq(directory.getDirectoryName()),
						argThat(new IsMockReporter()))).thenReturn(
				mockOutputCollectors.get(directory.getDirectoryName()));
	}
	mockOutputCollectors.put(
			OUTPUT_DIRECTORY_NAME,
			new MockOutputCollector());
	
	when(
			multipleOutputs.getCollector(
					eq(OUTPUT_DIRECTORY_NAME),
					argThat(new IsMockReporter()))).thenReturn(
				mockOutputCollectors.get(Constants.OUTPUT_DIRECTORY_NAME));
}

K3b-Plugin für Shuffle-unfähige CD-Player

Mein DVD-Player spielt zwar MP3-DVDs ab, kennt aber dummerweise keinen Random-Modus, spielt also alle Titel hintereinanderweg Album für Album durch. Als Gegenmaßnahme habe ich ein Plugin für K3b geschrieben, dass allen Dateien im Wurzelverzeichnis (mehr oder weniger) zufällige Namen gibt. Die Dateien heißen dann “titleXXXXX.ext”, die X werden durch Ziffern ersetzt, die Erweiterung wird beibehalten. Es basiert auf den K3b-Plugins audioprojectcddb und audiometainforenamer von Sebastian Trueg. Es arbeitet nur auf  neu erstellten Datenprojekten, nicht auf importieren Sessions.
k3bdataprojectrandomizerplugin

AerionInput aka spacenav_win32

Die Bauhaus-Universität Weimar hat ein sehr interessantes neues Eingabegerät erdacht, das die Navigation in 3D-Welten verbessern soll – den Axsotic oder aka GlobeFish aka Aerion. Dieses Eingabegerät stellt wie bisherige Geräte die Translation elastisch zur Verfügung, die Rotation jedoch wird intuitiv direkt gemappt und die Drehung der Kugel wird nicht wie sonst üblich über eine elastische Bewegung errechnet. Damit können Rotation und Translation vom Benutzer einfacher getrennt voneinander eingegeben werden.

Bisher wurde der Axsotic mit speziellen Avango-Plugins angesteuert, die leider nur in Avango funtionierten und daher nur für Demonstrationszwecke einsetzbar waren. Ich habe im Rahmen meiner Studienarbeit daher versucht, einen “Treiber” zu erstellen, der die Übermittlung der Daten zwischen Axsotic und Anwendung übernimmt. Das Ergebnis ist ein Windows-Framework, mit dem man nicht nur den Axsotic ansprechen kann, sondern auch die 3D-Eingabegeräte von 3DConnexion. Da ich ohnehin vorhatte, das Projekt unter die GPL zu stellen, und ich durch Zufall auf das Free Spacenav Projekt gestoßen bin, was über keinen Windows-Port verfügte, wurde nun AerionInput zu spacenav_win32. Damit steht nun auch unter Windows ein freier Treiber für die 3DConnexion-Hardware zum Download bereit. Leider im Moment nur in einer “unfreien” Sprache, es wird .NET 2.0 benötigt.

JAAS LoginModule for Linux Authentication

I was looking for a possibility to use the Linux passwd database authentication in Java in order to authenticate a user with a password against the local linux passwd database. Soon I realized that this is not possible out-of-the-box because there is no LoginModule for that. The project http://sourceforge.net/projects/jaas-pam/ seemed exactly the right thing for that but unfortunately it is outdated and lets Sun JRE 1.6 and IBM JRE 1.5 segfault. No matter if using the supplied binary lib or with a recompiled one, no avail.

So I implemented my own one, a JAAS LoginModule that authenticates against a local SSH Server using the sshj project. It opens up a connection to your local SSH server which must be running on the machine you want to authenticate against. Then it tries to log in the supplied user. If it succeeds the supplied username password pair is vaild on the server. You need the sshj library, mavens artifact is net.schmitzz.sshj.

import java.io.IOException;
import java.util.Map;

import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;

import net.schmizz.sshj.SSHClient;

public class SshLoginModule implements LoginModule {
	private CallbackHandler handler;
	private Subject subject;
	private String username;

	/**
	 * @see javax.security.auth.spi.LoginModule#abort()
	 */
	@Override
	public boolean abort() throws LoginException {
		return false;
	}

	/**
	 * @see javax.security.auth.spi.LoginModule#commit()
	 */
	@Override
	public boolean commit() throws LoginException {
		try {
			final DummyUserPrincipal user = new DummyUserPrincipal(
					username);
			final DummyRolePrincipal role = new DummyRolePrincipal(
					"admin");

			subject.getPrincipals().add(user);
			subject.getPrincipals().add(role);
			return true;

		} catch (final Exception e) {
			throw new LoginException(e.getMessage());
		}
	}

	/**
	 * @see javax.security.auth.spi.LoginModule#initialize(javax.security.auth.Subject,
	 *      javax.security.auth.callback.CallbackHandler, java.util.Map,
	 *      java.util.Map)
	 */
	@Override
	public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) {

		handler = callbackHandler;
		this.subject = subject;
	}

	/**
	 * @see javax.security.auth.spi.LoginModule#login()
	 */
	@Override
	public boolean login() throws LoginException {
		final Callback[] callbacks = new Callback[2];
		callbacks[0] = new NameCallback("username");
		callbacks[1] = new PasswordCallback("password", true);

		final SSHClient sshClient = new SSHClient();
		final String fingerprint = "33:**:**:**:..."; //15 hex digit fingerprint of your server.
		final String hostname = "localhost";

		try {
			handler.handle(callbacks);
			final String name = ((NameCallback) callbacks[0])
					.getName();
			final String password = String
					.valueOf(((PasswordCallback) callbacks[1])
							.getPassword());

			sshClient.addHostKeyVerifier(hostname, 22, fingerprint);
			sshClient.connect(hostname, 22);
			sshClient.authPassword(name, password);

			if (!sshClient.isAuthenticated()) {
				throw new LoginException(
						"Authentication failed for unknown reason.");
			}
			username = name;
			return true;

		} catch (final IOException e) {
			throw new LoginException("Authentication failed: "
					+ e.getMessage());
		} catch (final UnsupportedCallbackException e) {
			throw new LoginException("Authentication failed: "
					+ e.getMessage());
		} finally {
			try {
				sshClient.disconnect();
			} catch (final IOException e) {
				throw new LoginException("Authentication failed: "
						+ e.getMessage());
			}
		}
	}

	/**
	 * @see javax.security.auth.spi.LoginModule#logout()
	 */
	@Override
	public boolean logout() throws LoginException {
		try {
			final DummyUserPrincipal user = new DummyUserPrincipal(
					username);
			final DummyRolePrincipal role = new DummyRolePrincipal(
					"admin");
			subject.getPrincipals().remove(user);
			subject.getPrincipals().remove(role);
			return true;
		} catch (final Exception e) {
			throw new LoginException(e.getMessage());
		}
	}
}