Monday
Jun172013

PDF Emailer v2.1: Multiple recipient support

PDF Emailer v2.1 - multiple recipients

PDF Emailer is a iOS utility app that lets you email PDF and Office documents from Safari using a customizable email template.

In version 2.1, you can now set multiple recipients in your template by separating them with spaces.

PDF Emailer v2.1 is available for iPhone, iPad, and iPod Touch in the App Store.

Thursday
Mar072013

Java: Copy/Paste-Safe Logger Creation

We've all copied and pasted a Logger from one class to the other, forgetting to change the class name.

// WRONG
public class SomeNewClass
{
    // OOPS! I forgot to change the class name!
    private final Logger _logger = LoggerFactory.getLogger(SomeOtherClass.class);

    // …  
}

Here, use this smarter LoggerFactory which uses the stack trace to figure out what class you really mean. This is safe from copypasta laziness.

package org.blakecaldwell.logging;

import org.slf4j.Logger;

/**
 * Smart logger that uses reflection to figure out the logged class
 */
public final class ClassLoggerFactory
{
    /**
     * Disallow factory instances.
     */
    private ClassLoggerFactory()
    {
    }

    /**
     * Use the stack trace to determine the appropriate logger.
     * 
     * @return a logger for the direct caller's class.
     */
    public static Logger make()
    {
        Throwable t = new Throwable();
        StackTraceElement directCaller = t.getStackTrace()[1];
        return org.slf4j.LoggerFactory.getLogger(directCaller.getClassName());
    }
}

Then, to use this logger:

// CORRECT
public class SomeNewClass
{
    // No chance for screwing up anymore!
    private final Logger _logger = ClassLoggerFactory.make();

    // …  
}
Wednesday
Mar062013

Java: Embedding Spring Batch Admin Into An Existing Application

For my current project, I need to share Spring beans between my front-end web servlets and the Spring Batch jobs that I'll launch from Spring Batch Admin (SBA). This sounds straightforward - configure the SBA servlet to listen on /batch/*, and you should be good to go. However, of course it's not that easy.

URL Path Mismatch

The first problem you run into is bad links in the menus, form posts that go nowhere, and missing CSS files. This is because SBA expects to be deployed on /, not /batch, or /myapp/batch which is where it will go if you deploy it inside another app. You can survive in this mode for the most part by correcting menu URLs after you click them, or use FireBug to change form actions before you submit them - for example, from /batch/files to /myapp/batch/files, but who wants to live like an animal?

Spring Batch Admin 1.2.1 to the Rescue

Spring Batch Admin version 1.2.1 adds the ability to set the base servlet path for all links and forms by overriding the "resourceService" bean. I'm sure there are several ways to successfully accomplish this, but here's what worked for me.

pom.xml

Add the following repository:

<repository>
    <id>repository.springframework.maven.release</id>
    <name>Spring Framework Maven Release Repository</name>
    <url>http://maven.springframework.org/release</url>
</repository>

and the dependencies:

<dependency>
    <groupId>org.springframework.batch</groupId>
    <artifactId>spring-batch-admin-resources</artifactId>
    <version>1.2.1.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.batch</groupId>
    <artifactId>spring-batch-admin-manager</artifactId>
    <version>1.2.1.RELEASE</version>
</dependency>

web.xml

Configure the Batch Admin Servlet - notice contextConfigLocation:

<servlet>
    <servlet-name>Batch Admin Servlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring/batch-admin/batch-admin-context.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>Batch Admin Servlet</servlet-name>
    <url-pattern>/batch/*</url-pattern>
</servlet-mapping>

/WEB-INF/spring/batch-admin/batch-admin-context.xml

This is the context file that's only used by SBA. Keep in mind that any beans defined in your root Spring context will also be available to the jobs that are launched by SBA.

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

    <!-- Spring Batch Admin Context: Additional context for Spring Batch Admin -->
    <import resource="classpath*:/META-INF/spring/batch/servlet/resources/*.xml" />
    <import resource="classpath*:/META-INF/spring/batch/servlet/manager/*.xml" />
    <import resource="classpath*:/META-INF/spring/batch/servlet/override/*.xml" />
    <import resource="classpath*:/META-INF/spring/batch/bootstrap/**/*.xml" />
    <import resource="classpath*:/META-INF/spring/batch/override/**/*.xml" />

    <!-- For Spring Batch -->
    <bean id="resourceService"
        class="org.springframework.batch.admin.web.resources.DefaultResourceService">
        <property name="servletPath" value="/batch" />
    </bean>
</beans>

One Last Gotcha - /myapp/batch

The main "Home" link in the SBA action bar navigates to /myapp/batch. This doesn't work the way I've wired it up, because the SBA servlet is configured to listen to /myapp/batch/, but not /myapp/batch. I tried adding another servlet-mapping to /batch, but the servlet won't answer requests to it.

I'm sure there's a better way to do this via config only - please tell me if you wouldn't mind, but I opted for the more manual way, just so I could move on.

I wired up a servlet in my main web app to listen to /batch, and then have it redirect to /batch/.

web.xml - configuration for a servlet in my main app

Pay attention to the last servlet-mapping. This gives /myapp/batch over to Spring MVC for URL mapping.

<servlet>
    <servlet-name>appServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
    </init-param>
    <async-supported>true</async-supported>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>appServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

<!-- LOOK HERE -->
<servlet-mapping>
    <servlet-name>appServlet</servlet-name>
    <url-pattern>/batch</url-pattern>
</servlet-mapping>

HomeController.java

Here's where I redirect /batch to /batch/. Again, I'm sure there's a better way to do this (let me know!). Of course, for this specific code to work, you'll need some dependencies such as spring-webmvc. The bottom line is that I'm just manually listening on /batch and redirecting to /batch/.

@Controller
public class HomeController
{
    /**
     * Redirect the url /batch to /batch/ for the Spring Batch Admin to pick it up.
     * 
     * @return redirect to /batch/
     */
    @RequestMapping(value = "/batch", method = RequestMethod.GET)
    public String redirectBatchToBatchSlash()
    {
        return "redirect:/batch/";
    }
}

Know a Better Way?

Ideally, I'd like to either get Spring Batch Admin to listen to /batch or /myapp/batch, but I'd settle on having a redirect configured in web.xml. Please tell me if you know of a good approach. I moved on with this good-enough solution.

Thursday
Feb282013

Java: Maven Environment Variables on OS X

Mac OS X comes with Apache Maven preinstalled. However, you're still going to need to set some environment variables to get up and running.

Verify that maven installed in /usr/share/maven:

    ls /usr/share/maven

Add the following line to your ~/.profile

    # Set the M2_HOME environment variable
    export M2_HOME=/usr/share/maven

    # Put maven on your path
    export PATH=$M2_HOME/bin:$PATH

This won't take effect until you either reboot or open a new terminal window. You can reload the file in the current terminal window with the following command:

    source ~/.profile
Tuesday
Jul102012

Solving a Mobile PDF Problem

You find an important PDF document.

It's not unusual to stumble upon a PDF document while web surfing on your iPad, iPhone, or iPod Touch. You might be logged into your bank's site to view a monthly statement, or have finally found that important tax document that took you 10 minutes of poking and prodding a confusing online submission form.

You can't email it to yourself.

Share Options You don't have time to read the document now, and it occurs to you that you should probably share this with your spouse while you're at it, so you touch the "Share" button and look for "Email". This is where you realize that your only option is to email a link, not the document itself. This doesn't help you. You are not amused.

For me, that moment of disappointment came when I had opened up a bunch of tabs with interesting articles from an online version of a magazine that I subscribe to. Emailing myself links wouldn't work, because these PDF documents require you to login first, and for whatever reason, you can't just navigate to an article by their URL.

The solution is simple.

I solved this problem by developing the iOS App, PDF Emailer.
  1. While in Safari, touch the document once to bring up the options, and touch the "Open in..." button.
  2. Touch the "PDF Emailer" button.
  3. Enter the recipients and send the email.

PDF Emailer will keep your PDF document until you delete it. This allows you to read it while offline, or email it out again later.

It runs on the iPad, iPhone, and iPod Touch, and is available now on the App Store.