Quick and dirty implementation of the Java's InputStream, that can generate a desired number of random characters, without buffering it in memory. May be useful for unit testing.
import org.apache.commons.lang3.RandomStringUtils; import java.io.IOException; import java.io.InputStream; public class RandomCharsInputStream extends InputStream { private static final int ALPHABETIC = 1; private static final int ALPHANUMERIC = 2; private static final int ASCII = 3; private static final int NUMERIC = 4; private static final int CHARS = 5; private int type = ALPHABETIC; private long size = 0; private long charsRead = 0; private char[] chars; public static final RandomCharsInputStream newAlphabetic(long size) { return new RandomCharsInputStream(size, ALPHABETIC); } public static final RandomCharsInputStream newAlphanumeric(long size) { return new RandomCharsInputStream(size, ALPHANUMERIC); } public static final RandomCharsInputStream newAscii(long size) { return new RandomCharsInputStream(size, ASCII); } public static final RandomCharsInputStream newNumeric(long size) { return new RandomCharsInputStream(size, NUMERIC); } public static final RandomCharsInputStream newWithChars(long size, char... chars) { return new RandomCharsInputStream(size, chars); } public static final RandomCharsInputStream newWithChars(long size, String chars) { return new RandomCharsInputStream(size, chars.toCharArray()); } private RandomCharsInputStream(long size, int type) { this.size = size; this.type = type; } private RandomCharsInputStream(long size, char... chars) { this.size = size; this.type = CHARS; this.chars = chars; } @Override public int read() throws IOException { if (charsRead >= size) return -1; char c; switch (type) { case ALPHABETIC: c = RandomStringUtils.randomAlphabetic(1).charAt(0); break; case ALPHANUMERIC: c = RandomStringUtils.randomAlphanumeric(1).charAt(0); break; case ASCII: c = RandomStringUtils.randomAscii(1).charAt(0); break; case NUMERIC: c = RandomStringUtils.randomNumeric(1).charAt(0); break; case CHARS: c = RandomStringUtils.random(1, chars).charAt(0); break; default: throw new IllegalArgumentException("Unknown random type: " + type); } charsRead++; return c; } }
Usage examples:
RandomCharsInputStream in = RandomCharsInputStream.newAscii(10000); RandomCharsInputStream in = RandomCharsInputStream.newNumeric(100); RandomCharsInputStream in = RandomCharsInputStream.newWithChars(30, "xyz");
Anya is live and ready to show you everything. Watch her strip, dance, and perform exclusive shows just for you. Interact in real-time and make your fantasies come true.
✓ Live Streaming✓ Interactive Chat✓ Private Shows✓ HD Quality
Anya is LIVE right now
FREE
Free to watch • No registration required • HD streaming
Spring AOP: Intercepting method with custom annotation
Almost every Spring developer already seen and used @Transactional annotation, responsible for the database transaction demarcation.
It creates an AOP around advice, that starts the database tranaction before method execution and commits (or rollbacks) it, after the execution ends.
This post demonstrates, how to create a custom annotation working like @Transactional and responsible for profling method execution. It uses method intercepting capabilities provided by the Spring AOP, however without utilizing AspectJ.
1. We'll start with the custom annotation @ProfileExecution, that will mark bean methods we want to profile:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @Inherited @Documented public @interface ProfileExecution { }
2. Next create an method interceptor, that will measure the method execution time using Spring's StopWatch class:
public class ProfilingMethodInterceptor implements MethodInterceptor { private static final Logger log = LoggerFactory.getLogger(ProfilingMethodInterceptor.class); @Override public Object invoke(MethodInvocation invocation) throws Throwable { final StopWatch stopWatch = new StopWatch(invocation.getMethod().toGenericString()); stopWatch.start("invocation.proceed()"); try { log.info("~~~~~~~~ START METHOD {} ~~~~~~~~", invocation.getMethod().toGenericString()); return invocation.proceed(); } finally { stopWatch.stop(); log.info(stopWatch.prettyPrint()); log.info("~~~~~~~~ END METHOD {} ~~~~~~~~", invocation.getMethod().toGenericString()); } } }
3. Now we need a PointcutAdvisor implementation, that will match only Spring beans and those methods, marked with the @ProfileExecution annotation:
@Component public class ProfilingAdvisor extends AbstractPointcutAdvisor { private static final long serialVersionUID = 1L; private final StaticMethodMatcherPointcut pointcut = new StaticMethodMatcherPointcut() { @Override public boolean matches(Method method, Class<?> targetClass) { return method.isAnnotationPresent(ProfileExecution.class); } }; @Autowired private ProfilingMethodInterceptor interceptor; @Override public Pointcut getPointcut() { return this.pointcut; } @Override public Advice getAdvice() { return this.interceptor; } }
3. Last but not least is a Spring's application context XML, that will activate the ProfilingMethodInterceptor on all beans, using our annotation (see DefaultAdvisorAutoProxyCreator bean definition):
Embedded SSH daemon and remote shell for your java application
Wouldn't it be nice to have a possibility to connect you application securely via SSH with a secret user/password and communicate with it within the application specific shell? That was my first idea as I found the Apache's SSHD project.
Apache SSHD is a 100% pure java library to support the SSH protocols on both the client and server side. This library is based on Apache MINA, a scalable and high performance asynchronous IO library.
This blog post demonstrates how is it possible to create a application shell using Apache SSHD and JLine projects.
To start a new demo project we will use Maven's archetype:generate goal and create a new project skeleton:
Now we will put all neccessary dependencies to the project's pom.xml:
<dependency> <groupId>org.apache.sshd</groupId> <artifactId>sshd-core</artifactId> <version>0.9.0</version> </dependency> <dependency> <groupId>jline</groupId> <artifactId>jline</artifactId> <version>2.11</version> </dependency> <!-- not really necessary, just to simplify logging in this demo --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>1.6.6</version> </dependency>
We want our application shell to be secured by the username and password, thus we will implement SSHD's PasswordAuthenticator interface and accept in this demo all users, those password is equal to the username (surely in the real world application you have to use some user's datastore and only accept users that are allowed to connect your application via ssh).
public class InAppPasswordAuthenticator implements PasswordAuthenticator { @Override public boolean authenticate(String username, String password, ServerSession session) { return username != null && username.equals(password); } }
Next step it to implement the shell environment. To do this we have to create our own Factory implementation, what is able to handle user's I/O.
In this example I will use the JLine library for handling console input. It provides out of the box miscellaneous functionalities ("tab"-complition, command history, ..) and is very similar to BSD editline and GNU readline.
Our demo shell implementation will support following features:
output welcome banner on ssh session startup
"TAB"-complition support for following commands: quit, exit, version, help
entering "quit" or "exit" command will close the running SSH session
entering "version" command will output some dummy version string
entering "help" command will output "Help is not implemented yet..." string
any other input will be echoed back to the user
public class InAppShellFactory implements Factory { public Command create() { return new InAppShell(); } private static class InAppShell implements Command, Runnable { private static final Logger log = LoggerFactory.getLogger(InAppShell.class); public static final boolean IS_MAC_OSX = System.getProperty("os.name").startsWith("Mac OS X"); private static final String SHELL_THREAD_NAME = "InAppShell"; private static final String SHELL_PROMPT = "app> "; private static final String SHELL_CMD_QUIT = "quit"; private static final String SHELL_CMD_EXIT = "exit"; private static final String SHELL_CMD_VERSION = "version"; private static final String SHELL_CMD_HELP = "help"; private InputStream in; private OutputStream out; private OutputStream err; private ExitCallback callback; private Environment environment; private Thread thread; private ConsoleReader reader; private PrintWriter writer; public InputStream getIn() { return in; } public OutputStream getOut() { return out; } public OutputStream getErr() { return err; } public Environment getEnvironment() { return environment; } public void setInputStream(InputStream in) { this.in = in; } public void setOutputStream(OutputStream out) { this.out = out; } public void setErrorStream(OutputStream err) { this.err = err; } public void setExitCallback(ExitCallback callback) { this.callback = callback; } public void start(Environment env) throws IOException { environment = env; thread = new Thread(this, SHELL_THREAD_NAME); thread.start(); } public void destroy() { if (reader != null) reader.shutdown(); thread.interrupt(); } @Override public void run() { try { reader = new ConsoleReader(in, new FilterOutputStream(out) { @Override public void write(final int i) throws IOException { super.write(i); // workaround for MacOSX!! reset line after CR.. if (IS_MAC_OSX && i == ConsoleReader.CR.toCharArray()[0]) { super.write(ConsoleReader.RESET_LINE); } } }); reader.setPrompt(SHELL_PROMPT); reader.addCompleter(new StringsCompleter(SHELL_CMD_QUIT, SHELL_CMD_EXIT, SHELL_CMD_VERSION, SHELL_CMD_HELP)); writer = new PrintWriter(reader.getOutput()); // output welcome banner on ssh session startup writer.println("****************************************************"); writer.println("* Welcome to Application Shell. *"); writer.println("****************************************************"); writer.flush(); String line; while ((line = reader.readLine()) != null) { handleUserInput(line.trim()); } } catch (InterruptedIOException e) { // Ignore } catch (Exception e) { log.error("Error executing InAppShell...", e); } finally { callback.onExit(0); } } private void handleUserInput(String line) throws InterruptedIOException { if (line.equalsIgnoreCase(SHELL_CMD_QUIT) || line.equalsIgnoreCase(SHELL_CMD_EXIT)) throw new InterruptedIOException(); String response; if (line.equalsIgnoreCase(SHELL_CMD_VERSION)) response = "InApp version 1.0.0"; else if (line.equalsIgnoreCase(SHELL_CMD_HELP)) response = "Help is not implemented yet..."; else response = "======> \"" + line + "\""; writer.println(response); writer.flush(); } } }
Last but not least is to combine all this things together and start the embedded SSH server daemon on desired port (5222 in our demo):
public class App { public static void main(String[] args) throws Throwable { SshServer sshd = SshServer.setUpDefaultServer(); sshd.setPort(5222); sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider("hostkey.ser")); sshd.setPasswordAuthenticator(new InAppPasswordAuthenticator()); sshd.setShellFactory(new InAppShellFactory()); sshd.start(); } }
The KeyPairProvider above is used on the server side to provide the host key.
Now after starting our SSH daemon we can connect it using ordinary SSH client. Find below a screenshot of my session with the InAppShell:
The project source code is hosted on GitHub under https://github.com/bigpuritz/javaforge-blog/tree/master/sshd-daemon-demo
Some folder has to be polled for files (i will name them "header"-files), that contain only the meta-information (e.g. filesystem location) of the real "data"-file(s), that should be processed.
From the incoming "header"-file you have to read the data"-file(s) filesystem location and process it from there.
Both "header"- and "data"-file(s) should be moved to the particular "done"-folder after processing. Though the "header"-file is only allowed to be moved, if the appropriate "data"-file(s) was/were successfully processed.
In case the appropriate "data"-file does not exist, the "header"-file message should be rollbacked and the "header"-file must be processed once again after the specified timeout.
First, let's define our demo filesystem structure.
We have 2 incoming folders "headers" and "data", where the "headers"-folder contains the "header"-files and the "data"-folder the "data"-files respectively.
For the sake of convenience each "header"-file contain only one row with the name of the corresponding "data"-file. Something like this:
header-001.txt
data-001.txt
The first step is to create the route that polls the "headers"-folder, logs the "header"-file found, reads it's content as string and sends it as the message body to the subsequent node.
From now on the outgoing message body contains the content of the "header"-file, namely the name of the associated "data"-file.
Next step is to implement a processor, that consumes the "data"-file and send it as a new exchange forward to the subsequent recipient.
This is where I end up:
class HeaderFileBodyProcessor implements Processor { private String dataFileFolderURI; private String recipientEndpointURI; private int dataFileReadTimeout; HeaderFileBodyProcessor(String dataFileFolderURI, String recipientEndpointURI, int dataFileReadTimeout) { this.dataFileFolderURI = dataFileFolderURI; this.recipientEndpointURI = recipientEndpointURI; this.dataFileReadTimeout = dataFileReadTimeout; } @Override public void process(Exchange exchange) throws Exception { Exchange data = this.receiveBodyFile(exchange); this.sendToNextEndpoint(exchange, data); } private Exchange receiveBodyFile(Exchange originalExchange) throws Exception { String filename = originalExchange.getIn().getBody(String.class); ConsumerTemplate consumer = originalExchange.getContext().createConsumerTemplate(); try { return consumer.receive(this.dataFileFolderURI + "?fileName=" + filename, dataFileReadTimeout); } finally { // stop the consumer as it does not need to poll for files anymore consumer.stop(); } } private void sendToNextEndpoint(Exchange originalExchange, Exchange dataFileExchange) throws Exception { if (dataFileExchange != null) { ProducerTemplate prod = originalExchange.getContext().createProducerTemplate(); try { prod.send(this.recipientEndpointURI, dataFileExchange); } finally { prod.stop(); } } else { // rollback if no body file... throw new CamelExchangeException("Cannot find the body file " + originalExchange.getIn().getBody(String.class), originalExchange); } } }
For each incoming "data"-file name the processor creates a new file consumer, that tries to read the data-file within the specified timeout intervall. After the data file was read, the consumer will be explicitly stopped, since it does not need to poll for files anymore. In case, the data-file was found, it will be send as a new exchange to the next recipient, otherwise the CamelExchangeException will be thrown. This exception forces Apache Camel to rollback the incoming "header"-file Exchange and poll it againg later.
Extended camel route looks now like this:
There is only one thing that is still not really good enough in our route definition. It is blocking. On each incoming "header"-file the processing will wait for max. timeout period for the assocciated "data"-file, even if other "header"- and "data"-files are awailable and can be processed in parallel.
Fixing this is simple just by adding multi-threading possibilities to our route definition:
Some time ago I wrote an article about Apache Camel and how to send iOS push notifications using it. Now it's time to demonstrate how one can create Apache Camel custom components.
Before we start, some words about Apache Camel itself. What exactly is Apache Camel? Wikipedia says:
Apache Camel is a rule-based routing and mediation engine which provides a Java object-based implementation
of the Enterprise Integration Patterns using an API (or declarative Java Domain Specific Language) to configure routing and mediation rules.
But it seems that many people still don't understand the purpose of this framework. For those I would recommend to read the StackOverflow
discussion regarding this: http://stackoverflow.com/questions/8845186/what-exactly-is-apache-camel
One of the fundamental core elements of Apache Camel are components. Camel provides out of the box a rich set of pre-built components for nearly all common tasks enterprise developer may need nowadays while implementing Enterprise Integration Patterns (EIP). However, in rare cases it can happen that you have to develop a custom component, either to implement not yet covered task/usecase or just to encapsulate a complex route definition.
In this blog post i'll demonstrate, how to create a simple custom component that can repeatedly generate random character sequences.
The source code of the project discussed in this article can be found on GitHub: https://github.com/bigpuritz/javaforge-blog/blob/master/camel-rnd
First of all, let's define the URI scheme our component will provide:
rnd:someName?options
Notice here:
rnd: - an unique URI prefix, that identifies our component
someName - any string to uniquely identify the endpoint
options - query options in the following format, ?option=value&option=value&... In this way you can customize the component behavior.
Now let's start.
1. Using Maven and camel-archetype-component create a new project
This call will generate a new maven project containing following files:
META-INF/services/org/apache/camel/component/rnd - file to suppport auto-discovery of your component, where rnd is the URI scheme for your component and any related endpoints created on the fly. This file contains an one-line definition of the component class, that looks like this:
class=net.javaforge.blog.camel.rnd.RndComponent
RndComponent.java - the component class responsible for creaing an appropriate "rnd"-endpoint
RndEndpoint.java - is the Endpoint implementation, that will be referred in the DSL via its URI ("rnd:" in our case). Endpoints are responsible for creating Producer and Consumer instances, associated with it.
RndProducer.java - the Producer implementation for sending message exchanges to the endpoint.
RndConsumer.java - the Consumer implementation for consuming message exchanges from the endpoint.
2. Before we start with the Apache Camel specific implementation, let's first create the RndGenerator class, that can generate random character sequences. In this example I'll just use the pre-implemented random string generation functionality from commons-lang3 library and provide a random generator implemented as an enumeration supporting 5 generation types: RANDOM, ALPHABETIC, ALPHANUMERIC, NUMERIC and ASCII.
public enum RndGenerator { RANDOM { @Override public String generate() { if (chars != null) return RandomStringUtils.random(length, chars); else return RandomStringUtils.random(length, start, end, letters, numbers); } }, ALPHABETIC { @Override public String generate() { return RandomStringUtils.randomAlphabetic(length); } }, ALPHANUMERIC { @Override public String generate() { return RandomStringUtils.randomAlphanumeric(length); } }, NUMERIC { @Override public String generate() { return RandomStringUtils.randomNumeric(length); } }, ASCII { @Override public String generate() { return RandomStringUtils.randomAscii(length); } }; int length; String chars; boolean letters = false; boolean numbers = false; int start = 0; int end = 0; private RndGenerator() { this(10); } private RndGenerator(int length) { this.length = length; } public RndGenerator chars(String chars) { this.chars = chars; return this; } public RndGenerator letters(boolean letters) { this.letters = letters; return this; } public RndGenerator numbers(boolean numbers) { this.numbers = numbers; return this; } public RndGenerator length(int length) { this.length = length; return this; } public RndGenerator start(int start) { this.start = start; return this; } public RndGenerator end(int end) { this.end = end; return this; } /** * Generates random string sequence... * * @return random string */ public abstract String generate(); }
3. Now it is time to implement the RndEnpoint. Since we want our endpoint repeatedly generates random strings, it is easier to inherit from Camel's ScheduledPollEndpoint. It automatically provides support for endpoints which create polling consumers and can recognize "polling"-specific options like pollStrategy, initialDelay, delay and many others (for further details see http://camel.apache.org/polling-consumer.html).
Furthermore this class defines all component specific properties (generator, length,...), that we want to offer as URI options.
Last but not least, an endpoint implementation shouled provide a producer and consumer, associated with it. In our special case, there is no need in producer implementation, since we only consume polling events from the endpoint and on each event send a random character sequence to the next processor in the route.
Find below the brief endpoint implementation. For further details please consult the GitHub repository:
@UriEndpoint(scheme = "rnd") public class RndEndpoint extends ScheduledPollEndpoint { @UriParam private RndGenerator generator = RndGenerator.RANDOM; @UriParam private int length = 10; @UriParam private String chars = null; @UriParam private boolean letters = false; @UriParam private boolean numbers = false; @UriParam private int start = 0; @UriParam private int end = 0; public RndEndpoint() { } public RndEndpoint(String uri, RndComponent component) { super(uri, component); } public Producer createProducer() throws Exception { throw new RuntimeCamelException("Cannot produce to a RndEndpoint: " + getEndpointUri()); } public Consumer createConsumer(Processor processor) throws Exception { RndConsumer consumer = new RndConsumer(this, processor); configureConsumer(consumer); return consumer; } public boolean isSingleton() { return true; } // getter/setter skipped }
4. Remaining step is the consumer implementation, that is very straight forward in our case:
public class RndConsumer extends ScheduledPollConsumer { private final RndEndpoint endpoint; public RndConsumer(RndEndpoint endpoint, Processor processor) { super(endpoint, processor); this.endpoint = endpoint; } @Override protected int poll() throws Exception { Exchange exchange = endpoint.createExchange(); // create a message body exchange.getIn().setBody(generateRandomSequence()); try { // send message to next processor in the route getProcessor().process(exchange); return 1; // number of messages polled } finally { // log exception if an exception occurred and was not handled if (exchange.getException() != null) { getExceptionHandler().handleException( "Error processing exchange", exchange, exchange.getException()); } } } private String generateRandomSequence() { RndGenerator generator = endpoint.getGenerator(); ObjectHelper.notNull(generator, "generator"); return generator.length(endpoint.getLength()) .chars(endpoint.getChars()).letters(endpoint.isLetters()) .numbers(endpoint.isNumbers()).start(endpoint.getStart()) .end(endpoint.getEnd()).generate(); } }
That's it.. Now we are able to use our new custom component in the following fashion:
Repeatedly generate random alphabetic character sequences each 5 characters long and send them to the console output stream:
Every 50ms generate random alphanumeric character sequences each 10 characters long, aggregate them to the lists of 5 elements and send each list as a new message to the subsequent log component:
from("rnd:foo?generator=alphanumeric&initialDelay=0&delay=50&length=10") .setHeader("foo", constant("bar")) .aggregate(header("foo"), new ListAggregationStrategy()) .completionSize(5) .to("log:net.javaforge.blog.camel?level=INFO"); private static class ListAggregationStrategy implements AggregationStrategy { @SuppressWarnings("unchecked") @Override public Exchange aggregate(Exchange oldExchange, Exchange newExchange) { Object newBody = newExchange.getIn().getBody(); List<Object> list; if (oldExchange == null) { list = new ArrayList<Object>(); list.add(newBody); newExchange.getIn().setBody(list); return newExchange; } else { list = oldExchange.getIn().getBody(List.class); list.add(newBody); return oldExchange; } } }
Repeatedly (every 10ms) generate random alphanumeric character sequences each 50 characters long and append them to the existing file out.txt:
Anya is live and ready to show you everything. Watch her strip, dance, and perform exclusive shows just for you. Interact in real-time and make your fantasies come true.
✓ Live Streaming✓ Interactive Chat✓ Private Shows✓ HD Quality
Anya is LIVE right now
FREE
Free to watch • No registration required • HD streaming
Parallelizing execution with xargs on Unix-like operating systems
In my current project i have to build multiple Java projects with Ant.
xargs command line utility can accelerate the build process, by starting the necessary build scripts in parallel threads.
Below is a simple shell script build-all.sh, that finds all build.xml files within "build" subfolders and starts Ant for each of them with the specified command line parameters.
if [ -z "$1" ] then echo -e "Usage: build-all.sh " exit -1 fi find */build -name 'build.xml' -print0 | xargs -0 -n 1 -P 10 ant "$@" -f
Javassist is a really easy to use bytecode manipulation library.
Creating class proxies with Javassist is straight forward, just create an instance of ProxyFactory, a corresponding MethodHandler, that handles the method invocation and the class proxy itself.
Find below a small proxy creation example, that traces the execution of all class methods:
public class ProxyFactoryExample { public void foo() { System.out.println("Foo method executed."); } public void bar() { try { Thread.sleep(500); } catch (InterruptedException e) { // ignore } System.out.println("Bar method executed."); } public static void main(String[] args) throws Throwable { ProxyFactory proxy = new ProxyFactory(); proxy.setSuperclass(ProxyFactoryExample.class); proxy.setInterfaces(new Class[] { Serializable.class }); proxy.setFilter(new MethodFilter() { @Override public boolean isHandled(Method m) { // skip finalize methods return !(m.getParameterTypes().length == 0 && m.getName() .equals("finalize")); } }); MethodHandler tracingMethodHandler = new MethodHandler() { @Override public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable { long start = System.currentTimeMillis(); try { return proceed.invoke(self, args); } finally { long end = System.currentTimeMillis(); System.out.println("Execution time: " + (end - start) + " ms, method: " + proceed); } } }; ProxyFactoryExample obj = (ProxyFactoryExample) proxy.create( new Class[0], new Object[0], tracingMethodHandler); obj.foo(); obj.bar(); } }
Rendering docbook documents with docbook4j library
In my spare time i've created a small embeddable java library able to render docbook documents to the well known target output formats like PDF, HTML or RTF.
It can be downloaded here: http://code.google.com/p/docbook4j
Maven users please add following repository and dependency declarations to your POM-File:
This post gives a brief overview how it can be used.
First, depending on what output format you aim to produce, there are 3 possible renderer to use: PDFRenderer, HTMLRenderer and RTFRenderer.
Each renderer requires a Docbook XML resource as input. Optionally you can specify a XSL stylesheet and (only in case of HTMLRenderer) an optional CSS stylesheet to use.
As result each renderer produces an InputStream instance containing generated docbook output in appropriate format.
Note! As resource definition docbook4j supports all filesystem types supported by commons-vfs2 (see http://commons.apache.org/vfs/filesystems.html).
Following few examples demonstrate how docbook4j can be used.
Generate PDF input stream using specified Docbook XML (located within zip archive) and default Docbook XSL:
String xml = "zip:path/to/my/zip/docs.zip!document.xml"; PDFRenderer pdfRenderer = PDFRenderer.create(xml); InputStream in = pdfRenderer.render();
Generate PDF input stream using specified Docbook XML (located within classpath) and custom Docbook XSL (located within external zip archive):
String xml = "res:classpath/to/my/xml/document.xml"; String xsl = = "zip:path/to/my/xsl/zip/xsls.zip!styles.xsl"; PDFRenderer pdfRenderer = PDFRenderer.create(xml, xsl); InputStream in = pdfRenderer.render();
Generate HTML input stream using specified Docbook XML (located in filesystem) and custom CSS:
String xml = "file:///home/someuser/somedir/document.xml"; String css = "file:///home/someuser/somedir/other/my.css"; HTMLRenderer htmlRenderer = HTMLRenderer.create(xml).css(css). InputStream in = htmlRenderer.render();
Additionally some advanced docbook4j features and notes:
While writing a custom Docbook XSL, you usually want to import a default docbook XSL in your stylesheet. With docbook4j just use following xsl import definition:
With docbook4j you are able to use MVEL or OGNL to evaluate dynamic expressions at generation time. Following example gives a brief overview how to use this feature.
Note! To be able to use this feature, you have to add MVEL of OGNL library expicitely to your classpath.
Assume we have some Java Bean defining 3 properties: inceptionYear, name and version:
public class Project { private String inceptionYear; private String version; private String name; // getter / setter goes here }
Furthermore there is a docbook document, referencing this properties via MVEL expressions:
<?xml version="1.0" encoding="UTF-8"?> <book version="5.0" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xi="http://www.w3.org/2001/XInclude" xmlns:db="http://docbook.org/ns/docbook"> ... <copyright> <year><?mvel project.inceptionYear?></year> <holder>Company Inc. [COMPANY, INC.]. All Rights Reserved.</holder> </copyright> <abstract> <para> This is a documentation example generated with Docbook for project <?mvel project.name?> (v.<?mvel project.version?>). </para> </abstract> ... </book>
To be able to render such a document, we have to specify a variable, that will be used by the appropriate renderer to evaluate the expressions:
Project project = new Project(....); String xml = "...."; // xml document source PDFRenderer renderer = PDFRenderer.create(xml).variable("project", project); InputStream in = renderer.render();
Note! Instead of mvel, you can use ognl keyword like this: <?ognl ....some expression... ?>. This will use OGNL library instead of MVEL to evaluate the expressions.
Anya is live and ready to show you everything. Watch her strip, dance, and perform exclusive shows just for you. Interact in real-time and make your fantasies come true.
✓ Live Streaming✓ Interactive Chat✓ Private Shows✓ HD Quality
Anya is LIVE right now
FREE
Free to watch • No registration required • HD streaming
Oracle Client Package provides loadjava and dropjava tools to loading/dropping java classes / jars / resources to/from the Oracle database.
Sometimes however it is necessary to run this functionality on the machine that doesn't have Oracle Client package installed.
This post describes how to achieve this using Ant.
Note! This instruction is for Oracle 11g.
Prerequisites
From the machine having Oracle Client installed, copy ojdbc5.jar (typically located in $ORACLE_HOME/product/11.x/client_1/jdbc/lib) and aurora.zip (typically located in $ORACLE_HOME%/product/11.x/client_1/javavm/lib) to some folder accessible by your Ant script.
Below i'll assume, that this 2 files are located in the same folder where the Ant script located.
In the changelog above we make use of the <loadData> tag that is able to load data from the CSV file and insert it into the database (alternatively you may use <insert>, <update> and <delete> tags to manipulate the database contents). Furthermore the <rollback> block describes how to remove the inserted changes from the database.
As brief overview, here is an example of the roles.csv file:
name,description USER,A simple user ADMIN,Administrator user ANONYMOUS,NULL
First row in the CSV file specifies the column names to populate. All subsequent rows contains the test data.
Please consult GitHub for other CSV files used in this post: https://github.com/bigpuritz/javaforge-blog/tree/master/liquibase-sample/db/testdata
Now, let's make our project ready to use Liquibase together with the Junit.
Add following test-scoped dependencies to the pom.xml:
<dependencies> <dependency> <!-- only necessary since we're using H2 as our demonstration database --> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>1.3.168</version> <scope>test</scope> </dependency> <dependency> <groupId>org.liquibase</groupId> <artifactId>liquibase-core</artifactId> <version>2.0.5</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> <scope>test</scope> </dependency> </dependencies>
In the next step we'll create a skeleton for our testcase, that will put the test data into the database only once on startup and remove it on teardown after all tests are executed.
public class TestWithLiquibase { private static Connection conn; private static Liquibase liquibase; @BeforeClass public static void createTestData() throws SQLException, ClassNotFoundException, LiquibaseException { Class.forName("org.h2.Driver"); conn = DriverManager.getConnection("jdbc:h2:liquibase-sample", "sa", "sa"); Database database = DatabaseFactory.getInstance() .findCorrectDatabaseImplementation(new JdbcConnection(conn)); liquibase = new Liquibase("db/testdata/db.testdata.xml", new FileSystemResourceAccessor(), database); liquibase.update(null); } @AfterClass public static void removeTestData() throws LiquibaseException, SQLException { liquibase.rollback(1000, null); conn.close(); } }
Now, writing a database-driven unit tests is quite simple. Following few tests just giving an idea, how to do this:
Consult https://github.com/bigpuritz/javaforge-blog/tree/master/liquibase-sample for the sample project sources.
Quote:
Liquibase is an open source (Apache 2.0 Licensed), database-independent library for tracking, managing and applying database changes.
It is built on a simple premise: All database changes are stored in a human readable yet trackable form and checked into source control.
This post is a simple tutorial demonstrating how to use Liquibase in a real world project. We'll assume that our sample project lives through multiple phases, each of which adds diverse changes to the database.
Let's prepare our sample project to use Liquibase within Maven build first. We need to define the liquibase-maven-plugin within the <plugins>...</plugins> block and point it to the liquibase.properties file, containing all properties required by the Liquibase at runtime. Both are demonstrated below.
As you may note, liquibase.properties file references the db.changelog-master.xml file. This file is an entry point for Liquibase and contains all our database changes.
In our sample project it has include definitions, referencing to the changes affecting the major versions only (1.x, 2.x etc):
Now we are ready to apply our changes against the database. To do this, just execute the following maven command:
mvn liquibase:update
After execution you'll see, that following database objects were created by the Liquibase:
As you may note, Liquibase additionaly created two special tables DATABASECHANGELOG and DATABASECHANGELOGLOCK containing metadata required by the Liquibase at runtime.
Below is a contents of the DATABASECHANGELOG table after executing our first database changelog:
Note! If you don't want to apply the changes directly to the database, but to generate a sql sciprt instead, run the following maven command:
mvn liquibase:updateSQL
This will generate a migration sql script migrate.sql into the target/liquibase folder. For our first database changelog this script looks like this:
-- ********************************************************************* -- Update Database Script -- ********************************************************************* -- Change Log: db/ddl/db.changelog-master.xml -- Ran at: 06/11/12 09:23 -- Against: SA@jdbc:h2:liquibase-sample -- Liquibase version: 2.0.5 -- ********************************************************************* -- Create Database Lock Table CREATE TABLE DATABASECHANGELOGLOCK (ID INT NOT NULL, LOCKED BOOLEAN NOT NULL, LOCKGRANTED TIMESTAMP, LOCKEDBY VARCHAR(255), CONSTRAINT PK_DATABASECHANGELOGLOCK PRIMARY KEY (ID)); INSERT INTO DATABASECHANGELOGLOCK (ID, LOCKED) VALUES (1, FALSE); -- Lock Database -- Create Database Change Log Table CREATE TABLE DATABASECHANGELOG (ID VARCHAR(63) NOT NULL, AUTHOR VARCHAR(63) NOT NULL, FILENAME VARCHAR(200) NOT NULL, DATEEXECUTED TIMESTAMP NOT NULL, ORDEREXECUTED INT NOT NULL, EXECTYPE VARCHAR(10) NOT NULL, MD5SUM VARCHAR(35), DESCRIPTION VARCHAR(255), COMMENTS VARCHAR(255), TAG VARCHAR(255), LIQUIBASE VARCHAR(20), CONSTRAINT PK_DATABASECHANGELOG PRIMARY KEY (ID, AUTHOR, FILENAME)); -- Changeset db/ddl/1.x/1.0/db.changelog-1.0.xml::1.0_1::mk::(Checksum: 3:f5d02804c70da7f8eda3e5eda3c987b3) CREATE TABLE t_user (id INT NOT NULL, name VARCHAR(50) NOT NULL, active BOOLEAN DEFAULT TRUE, CONSTRAINT PK_T_USER PRIMARY KEY (id)); INSERT INTO DATABASECHANGELOG (AUTHOR, COMMENTS, DATEEXECUTED, DESCRIPTION, EXECTYPE, FILENAME, ID, LIQUIBASE, MD5SUM, ORDEREXECUTED) VALUES ('mk', '', NOW(), 'Create Table', 'EXECUTED', 'db/ddl/1.x/1.0/db.changelog-1.0.xml', '1.0_1', '2.0.5', '3:f5d02804c70da7f8eda3e5eda3c987b3', 1); -- Changeset db/ddl/1.x/1.0/db.changelog-1.0.xml::1.0_2::mk::(Checksum: 3:55092b96f60bb8379f251b553c7ddc1e) ALTER TABLE t_user ALTER COLUMN name RENAME TO username; CREATE TABLE t_role (name varchar2(50) NOT NULL, description varchar2(250), CONSTRAINT PK_T_ROLE PRIMARY KEY (name)); ALTER TABLE t_user ADD role varchar2(50) NOT NULL; ALTER TABLE t_user ADD CONSTRAINT fk_user_role FOREIGN KEY (role) REFERENCES t_role (name); INSERT INTO DATABASECHANGELOG (AUTHOR, COMMENTS, DATEEXECUTED, DESCRIPTION, EXECTYPE, FILENAME, ID, LIQUIBASE, MD5SUM, ORDEREXECUTED) VALUES ('mk', '', NOW(), 'Rename Column, Create Table, Add Column, Add Foreign Key Constraint', 'EXECUTED', 'db/ddl/1.x/1.0/db.changelog-1.0.xml', '1.0_2', '2.0.5', '3:55092b96f60bb8379f251b553c7ddc1e', 2); -- Changeset db/ddl/1.x/1.0/db.changelog-1.0.xml::1.0_3::xyz::(Checksum: 3:b97854eff530d4d96b3fe096b308e239) CREATE INDEX indx_t_user_0003 ON t_user(username); CREATE VIEW v_user AS select u.id, u.username, r.name from t_user u join t_role r on u.role = r.name; INSERT INTO DATABASECHANGELOG (AUTHOR, COMMENTS, DATEEXECUTED, DESCRIPTION, EXECTYPE, FILENAME, ID, LIQUIBASE, MD5SUM, ORDEREXECUTED) VALUES ('xyz', '', NOW(), 'Create Index, Create View', 'EXECUTED', 'db/ddl/1.x/1.0/db.changelog-1.0.xml', '1.0_3', '2.0.5', '3:b97854eff530d4d96b3fe096b308e239', 3); -- Changeset db/ddl/1.x/1.0/db.changelog-1.0.xml::1.0_4::foobar::(Checksum: 3:2436467c358c5141a858c2705374d989) UPDATE DATABASECHANGELOG SET TAG = '1.0' WHERE DATEEXECUTED = (SELECT MAX(DATEEXECUTED) FROM DATABASECHANGELOG); INSERT INTO DATABASECHANGELOG (AUTHOR, COMMENTS, DATEEXECUTED, DESCRIPTION, EXECTYPE, FILENAME, ID, LIQUIBASE, MD5SUM, ORDEREXECUTED, TAG) VALUES ('foobar', '', NOW(), 'Tag Database', 'EXECUTED', 'db/ddl/1.x/1.0/db.changelog-1.0.xml', '1.0_4', '2.0.5', '3:2436467c358c5141a858c2705374d989', 4, '1.0');
And one more remark:
To rollback the applyed changes, simply run:
mvn liquibase:rollback
Step 2.
Now assume we are done with the version 1.0 and starting to develop a new version 1.1. This contains some database changes too, so we have to create a new changelog file and reference it within db.changelog-1.x.xml:
Once again to apply the changes, just run mvn liquibase:update. This time Liquibase recognizes, that the changes from db.changelog-1.0.xml were already applyed to the databse (remember the DATABASECHANGELOG table!) and applies only outstanding changes from db.changelog-1.1.xml.
Step 3.
Analog to the step 2 it specifies new changes for the next development version 1.2:
Using XSDs or any other custom resources as Maven dependencies
Often it is required to put some custom resources (e.g. XSDs, WSDLs etc.) under dependency management control.
As soon as Maven is a de facto standard dependency management mechanism in the Java world, this post demonstrates how to use it to manage untypical custom resources as dependencies within the pom.xml.
Let's assume we have some xsd file (test.xsd in this example), that we want to use as dependency in our POM.
First, we'll deploy it to the local repository (alternatively you can deploy it to your company's internal maven repository):
Furthermore if you want to add this file as a resource to the final artifact generated by Maven, you have to configure maven-dependency-plugin in your POM like this:
Releasing maven artifacts using Ant and Maven Ant Tasks
In the current project one of my tasks was to simplify the maven release process. This should be a simple one click solution, that satisfies following requirements:
Release has to be executed either from IDE or from the command line.
Everyone (not only developer) should be able to execute release.
It should hide the pain of executing multiple maven goals (release:prepare, release:perform) after each other.
After releasing the software released artifacts for all profiles specified in the pom.xml should be deployed to the company's maven repository.
I decided to write a simple Ant script that internally makes use of Maven Ant Tasks.
First a target asking user for all required release informations was born:
<?xml version="1.0" encoding="UTF-8"?> <project name="myprj" default="release" xmlns:artifact="antlib:org.apache.maven.artifact.ant"> <property environment="env" /> <path id="maven-ant-tasks.classpath" path="maven-ant-tasks-2.1.3.jar" /> <typedef resource="org/apache/maven/artifact/ant/antlib.xml" uri="antlib:org.apache.maven.artifact.ant" classpathref="maven-ant-tasks.classpath" /> <!-- - - - - - - - - - - - - - - - - - target: prerequisites - - - - - - - - - - - - - - - - - --> <target name="prerequisites"> <fail unless="env.MAVEN_HOME" message="Environment variable MAVEN_HOME was not found on your system!" /> </target> <!-- - - - - - - - - - - - - - - - - - target: input - - - - - - - - - - - - - - - - - --> <target name="input"> <artifact:pom id="local.pom" file="pom.xml" /> <script language="javascript"> <![CDATA[ var before = project.getProperty("local.pom.version"); project.setProperty("tmp.release.version", before.replaceAll("-SNAPSHOT", "")); var tmpVersion = project.getProperty("tmp.release.version"); var nextVersionNo = tmpVersion.substr( tmpVersion.lastIndexOf(".") + 1 ); project.setProperty("tmp.next.version", tmpVersion.substr(0, tmpVersion.lastIndexOf(".") + 1) + (parseInt(nextVersionNo)+1) + "-SNAPSHOT" ); ]]> </script> <input message="Please enter the project release version?" defaultvalue="${tmp.release.version}" addproperty="prj.release.version" /> <input message="Please enter the svn tag name?" defaultvalue="${local.pom.artifactId}-${prj.release.version}" addproperty="prj.release.tag" /> <input message="Please enter the project next version?" defaultvalue="${tmp.next.version}" addproperty="prj.next.version" /> <fail unless="prj.release.version" message="Property 'prj.release.version' was not defined!" /> <fail unless="prj.release.tag" message="Property 'prj.release.tag' was not defined!" /> <fail unless="prj.next.version" message="Property 'prj.next.version' was not defined!" /> </target> </project>
The "input"-target uses <artifact:pom ... /> task and offers the POM structure to the ant script.
Furthermore it makes use of Ant's possibility to embed javascript, that reads current artifact version from the POM and pre-set some default variables like release version, release tag and next development version.
Finally the second target specifies the release procedure:
Anya is live and ready to show you everything. Watch her strip, dance, and perform exclusive shows just for you. Interact in real-time and make your fantasies come true.
✓ Live Streaming✓ Interactive Chat✓ Private Shows✓ HD Quality
Anya is LIVE right now
FREE
Free to watch • No registration required • HD streaming
Sending iOS push notifications using Apache Camel and Boxcar.io
Apache Camel is a rule-based routing and mediation engine providing implementation of Enterprise Integration Patterns. It allows message transfer from different sources to different destinations (see http://camel.apache.org/components.html for further details).
Playing around with Apache Camel i've decided to write a simple example sending push notifications to the iOS devices.
One option is to use original Camel's Apns Component. This requires however your APNS provider has to be registered by Apple in order to send push notifications using generated certificate.
Another option is to use a free, public available APNS provider. In this example I'll use Boxcar.IO as an APNS provider.
Boxcar allows you to create your own provider (= boxcar account) and send push notifications through it to the free Boxcar App installed on your iOS device. Alternatively (for testing or development purpose) one of the pre-configured, generic providers can be used (i'll use generic "Monitoring" provider in this post).
Furthermore Boxcar exposes a very simple REST API containing only 3 RESTful requests: SUBSCRIBE, CREATE and BROADCAST. SUBSCRIBE will add your service for a user. CREATE sends a message to one user, or a subset of your users. BROADCAST sends a single message to all of your users.
Let's prepare the iOS device for using Boxcar first.
Sign up for an account by Boxcar.IO with your email address
Download and install free Boxcar App: http://itunes.apple.com/app/boxcar/id321493542?ign-mpt=uo%3D6&mt=8
Send a SUBSCRIBE request via Boxcar to your iPhone to activate one of the generic providers.
Now we are able to send push notifications to our iOS device through Boxcar.
Next step is to create Apache Camel routes by implementing following scenario:
If somebody drops a new file into the predefined folder location, new iOS push notification should be sent.
If somebody sends a HTTP request to our application with the request parameter "msg", its value will be taken as a message and sent as an iOS push notification.
We'll start with the Maven dependencies required to fulfill the task above:
Next we have to define our 2 route by subclassing Camel's RouteBuilder:
public class BoxcarRouteBuilder extends RouteBuilder { private static final String BOXCAR_QUERY_PREFIX = "[email protected]¬ification[from_screen_name]=Javaforge Blog¬ification[message]="; @Override public void configure() throws Exception { from("jetty:http://0.0.0.0:8888/messages") .setBody(simple(BOXCAR_QUERY_PREFIX + "${in.headers.msg}")) .to("log:net.javaforge.blog.camel.LOG") .to("http://boxcar.io/devices/providers/MH0S7xOFSwVLNvNhTpiC/notifications?bridgeEndpoint=true") .transform().constant("OK"); from("file:target/inbox?noop=true") .setBody(simple(BOXCAR_QUERY_PREFIX + "File added: ${file:path}")) .to("log:net.javaforge.blog.camel.LOG") .to("http://boxcar.io/devices/providers/MH0S7xOFSwVLNvNhTpiC/notifications"); } }
Some explanations:
First route definition listens for incoming HTTP requests on port 8888, reads the value of the request parameter "msg", rewrites the message body to the format required by Boxcar and forwards it to the Boxcar's generic provider. See Apache Camel's Jetty component for further details: http://camel.apache.org/jetty.html
Second route definition monitors "target/inbox" folder. Once new file is added to it, new message body with the name of the added file will be created and forwarded to the Boxcar. See Apache Camel's File component for further details: http://camel.apache.org/file2.html
Last step is to start Apache Camel in standalone mode and to provide BoxcarRouteBuilder to it:
public class BoxcarRunner { public static void main(String[] args) throws Exception { Main main = new Main(); // enable hangup support so you can press ctrl + c to terminate the JVM main.enableHangupSupport(); // add routes main.addRouteBuilder(new BoxcarRouteBuilder()); // run until you terminate the JVM System.out.println("Starting Camel. Use ctrl + c to terminate the JVM.\n"); main.run(); } }
Now we can either drop some files into the "target/inbox" folder or send HTTP requests to the localhost:8888/messages like this:
Atmosphere is a WebSocket/Comet Framework for Java (and other JVM languages) supporting transparently WebSockets, Server Side Events (SSE), Long-Polling, HTTP Streaming (Forever frame) and JSONP. The Atmosphere Framework is portable and can be deployed on any Web Server that supports the Servlet Specification 2.3.
This post demonstrates how to create a "per session" Atmosphere Broadcaster. One that uniquely associates itself with a client's http session and forms this way some kind of durable unique "communication channel" between the server and the client.
To start a project following maven dependency is required in your pom.xml:
Next we have to a create a HttpSessionListener implementation, instantiating new atmosphere broadcaster with a unique name each time a new http session is created and removing it, once this session will be destroyed by the container.
public class BroadcasterCreater implements HttpSessionListener { private static final Logger LOG = LoggerFactory.getLogger(BroadcasterCreater.class); public static final String BROADCASTER_ID_KEY = "net.javaforge.blog.atmosphere.broadcasterId"; @Override public void sessionCreated(HttpSessionEvent se) { HttpSession session = se.getSession(); String broadcasterId = "/broadcaster/" + session.getId(); LOG.info("Creating broadcaster: {}", broadcasterId); BroadcasterFactory.getDefault().lookup(broadcasterId, true); session.setAttribute(BROADCASTER_ID_KEY, broadcasterId); } @Override public void sessionDestroyed(HttpSessionEvent se) { HttpSession session = se.getSession(); String broadcasterId = (String) session.getAttribute(BROADCASTER_ID_KEY); LOG.info("Removing broadcaster: {}", broadcasterId); BroadcasterFactory.getDefault().remove(broadcasterId); } }
Once new http session is created by the server, new atmosphere broadcaster will be instantiated by calling #lookup(..) method on the default BroadcasterFactory. Furthemore the identifier of the created broadcaster will be put into the http session as attribute under the BROADCASTER_ID_KEY key.
Next step is to implement an Atmosphere Handler, that opens our "communication channel" and responds to the incoming requests or pushes some messages to the client. In this example we'll create just a "dummy" handler, that responds to the client with the same message it receives.
@AtmosphereHandlerService public class UserSessionAwareAtmosphereHandler extends AbstractReflectorAtmosphereHandler { private static final Logger LOG = LoggerFactory.getLogger(UserSessionAwareAtmosphereHandler.class); @Override public void onRequest(AtmosphereResource resource) throws IOException { suspendAtmosphereResourceIfNecessary(resource); if ("POST".equalsIgnoreCase(resource.getRequest().getMethod())) { doBroadcast(resource); } } private void doBroadcast(AtmosphereResource resource) throws IOException { AtmosphereRequest req = resource.getRequest(); String incomingMessage = req.getReader().readLine(); LOG.info("Incoming message: '{}'", incomingMessage); Broadcaster broadcaster = null; if (incomingMessage.startsWith("@/broadcaster/")) { String broadcasterId = incomingMessage.substring(1, incomingMessage.indexOf(" ")); broadcaster = lookupBroadcaster(broadcasterId); } else { broadcaster = lookupBroadcaster(req); } LOG.info("Broadcasting message with broadcaster = {}", broadcaster.getID()); broadcaster.broadcast("ACK! Sent message was: <strong>" + incomingMessage + "</strong>"); } private void suspendAtmosphereResourceIfNecessary( AtmosphereResource resource) { AtmosphereRequest req = resource.getRequest(); AtmosphereResponse resp = resource.getResponse(); String method = req.getMethod(); if ("GET".equalsIgnoreCase(method)) { LOG.info("GET request detected, suspending broadcaster..."); // Log all events on the console, including WebSocket events. resource.addEventListener(new WebSocketEventListenerAdapter()); resp.setContentType("text/html;charset=ISO-8859-1"); Broadcaster b = lookupBroadcaster(req); resource.setBroadcaster(b); if (req.getHeader(HeaderConfig.X_ATMOSPHERE_TRANSPORT) .equalsIgnoreCase(HeaderConfig.LONG_POLLING_TRANSPORT)) { req.setAttribute(ApplicationConfig.RESUME_ON_BROADCAST, Boolean.TRUE); resource.suspend(-1, false); } else { resource.suspend(-1); } LOG.info("Broadcasting notification message to all connected users..."); MetaBroadcaster.getDefault().broadcastTo( "/broadcaster/*", "new broadcaster connected: <strong>" + b.getID() + "</strong>"); } } @Override public void destroy() { } private Broadcaster lookupBroadcaster(AtmosphereRequest req) { String broadcasterId = (String) req.getSession().getAttribute(BroadcasterCreater.BROADCASTER_ID_KEY); return lookupBroadcaster(broadcasterId); } private Broadcaster lookupBroadcaster(String broadcasterId) { LOG.info("Looking up for broadcaster: {}", broadcasterId); Broadcaster broadcaster = BroadcasterFactory.getDefault().lookup(broadcasterId); LOG.info("Broadcaster found : {}", broadcaster.getID()); return broadcaster; } }
Some explanations:
lookupBroadcaster(..) method scans BroadcasterFactory and returns a broadcaster instance with the requested identifier. This broadcaster definitely exists already, because it was created by out http session listener one step earlier.
suspendAtmosphereResourceIfNecessary(..) method implements a typical Atmosphere way to handle the first incoming GET request. It initiates hierby a durable communication channel. Furthermore note the usage of the Atmosphere's MetaBroadcaster, sending notification to all connected broadcasters using "/broadcaster/*" pattern.
doBroadcast(..) method investigates the incoming message. If it starts with a special "@/broadcaster/" prefix, then the handler responds to the broadcater explicitly mentioned in the message (this way you can send messages to other connected broadcasters/http sessions). Otherwise it responds to the broadcaster associated with the current http session.
Note! Atmosphere is not only able to respond to the incoming message. Due the durable communication channel between server and client you can push messages to the client every time you want using Broadcaster.broadcast(..) method.
Next step is to connect all this things together by specifying them in the web.xml:
The only thing that needs to be demonstrated, is the message processing on the clientside. This can be done by adding the jquery and the jquery.atmosphere.js libraries to your clientside code and by writing some javascript handling the communication.
Javascript snippet below briefly demonstrates how the clientside code can be implemented:
$(document).ready( function() { function subscribe() { var request = { url : document.location.toString() + 'atmosphere/<%=broadcasterId%>', transport : 'websocket' }; request.onMessage = function(response) { // handle incoming message detectedTransport = response.transport; if (response.status == 200) { var data = response.responseBody; if (data.length > 0) { // do something with the data.... } } }; subSocket = socket.subscribe(request); } function unsubscribe() { socket.unsubscribe(); } function connect() { unsubscribe(); subscribe(); } function sendMessage(){ var msg = .... ; // message to send subSocket.push({ data : msg ? msg : "<null>" }); } });
Now you are able to support a durable per-session connection in your web application.
Please consult the GitHub for the complete working example: https://github.com/bigpuritz/javaforge-blog/tree/master/atmosphere-per-session-broadcaster
Check out the sources and execute mvn clean install jetty:run in your console. This will build the project and start the embedded Jetty web server instance.