WWDC Announced

In an unusual move by Apple they have announced the date and ticket sales ahead of time. This morning around 5:30 am PT I was waken to my monitoring app and greeted with this page.

WWDC Page

So now we’ll have to see how fast those tickets sell out. Last year it was around 2 hours, I’m guessing between 30 minutes and 1 hour this year. Let’s hope apple handles the load a little better than google did for google I/O

Setting up nginx on Ubuntu 12.04 LTS for a test server

This guide walks through the steps of setting up nginx on a test server for use with a WordPress site. This guide assumes you already have Ubuntu 12.04 installed.

For this guide we will be setting up nginx from source.  First we need to install the pre-requisites.

1
sudo apt-get install libpcre3-dev build-essential libssl-dev

Next you will need to download the source from the nginx download page For this article we will assume the current version is 1.2.8.

1
2
3
4
5
cd /opt/
sudo wget http://nginx.org/download/nginx-1.2.8.tar.gz
sudo tar xvzf nginx-1.2.8.tar.gz
cd /opt/nginx-1.2.8/
sudo ./configure --prefix=/opt/nginx --user=nginx --group=nginx --with-http_ssl_module

After the the configure step please review the location of the configuration files. It should be displayed in the output of the configure command. After that we can compile and install it.

1
2
sudo make
sudo make install

Now we need to create the user for the nginx process.

1
sudo adduser --system --no-create-home --disabled-login --disabled-password --group nginx

Next we need to create a startup script in /etc/init.d/nginx

1
sudo vi /etc/init.d/nginx

Then add the following to the bash script.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#! /bin/sh
 
### BEGIN INIT INFO
# Provides:          nginx
# Required-Start:    $all
# Required-Stop:     $all
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: starts the nginx web server
# Description:       starts nginx using start-stop-daemon
### END INIT INFO
 
PATH=/opt/nginx/sbin:/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/opt/nginx/sbin/nginx
NAME=nginx
DESC=nginx
 
test -x $DAEMON || exit 0
 
# Include nginx defaults if available
if [ -f /etc/default/nginx ] ; then
        . /etc/default/nginx
fi
 
set -e
 
case "$1" in
  start)
        echo -n "Starting $DESC: "
        start-stop-daemon --start --quiet --pidfile /opt/nginx/logs/$NAME.pid \
                --exec $DAEMON -- $DAEMON_OPTS
        echo "$NAME."
        ;;
  stop)
        echo -n "Stopping $DESC: "
        start-stop-daemon --stop --quiet --pidfile /opt/nginx/logs/$NAME.pid \
                --exec $DAEMON
        echo "$NAME."
        ;;
  restart|force-reload)
        echo -n "Restarting $DESC: "
        start-stop-daemon --stop --quiet --pidfile \
                /opt/nginx/logs/$NAME.pid --exec $DAEMON
        sleep 1
        start-stop-daemon --start --quiet --pidfile \
                /opt/nginx/logs/$NAME.pid --exec $DAEMON -- $DAEMON_OPTS
        echo "$NAME."
        ;;
  reload)
          echo -n "Reloading $DESC configuration: "
          start-stop-daemon --stop --signal HUP --quiet --pidfile     /opt/nginx/logs/$NAME.pid \
              --exec $DAEMON
          echo "$NAME."
          ;;
      *)
            N=/etc/init.d/$NAME
            echo "Usage: $N {start|stop|restart|reload|force-reload}" >&2
            exit 1
            ;;
    esac
 
    exit 0

Next we’ll make the script executable and have it run at startup.

1
2
sudo chmod +x /etc/init.d/nginx
sudo /usr/sbin/update-rc.d -f nginx defaults

Now start the server

1
sudo /etc/init.d/nginx start

Now we need to configure PHP using FastCGI. Execute the following commands install FastCGI for PHP.

1
sudo apt-get install php5-cli php5-cgi psmisc spawn-fcgi

Create the following file using your favoriate linux/unix text editor at /usr/bin/php-fastcgi.

1
sudo vi /usr/bin/php-fastcgi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/sh
 
if [ `grep -c "nginx" /etc/passwd` = "1" ]; then 
   FASTCGI_USER=nginx
elif [ `grep -c "www-data" /etc/passwd` = "1" ]; then 
   FASTCGI_USER=www-data
elif [ `grep -c "http" /etc/passwd` = "1" ]; then 
   FASTCGI_USER=http
else 
# Set the FASTCGI_USER variable below to the user that 
# you want to run the php-fastcgi processes as
 
FASTCGI_USER=
fi
 
SOCKET=/var/run/php-fastcgi/php-fastcgi.socket
PIDFILE=/var/run/php-fastcgi/php-fastcgi.pid
CHILDREN=6
PHP5=/usr/bin/php5-cgi
 
/usr/bin/spawn-fcgi -s $SOCKET -P $PIDFILE -C $CHILDREN -u $FASTCGI_USER -g $FASTCGI_GROUP -f $PHP5

At this point you want to make sure you have a folder in /var/run/php-fastcgi. If not you will need to create it and give the nginx user permission to it.

1
2
sudo mkdir /var/run/php-fastcgi
sudo chown nginx:root /var/run/php-fastcgi

Now we will create the init script for fastcgi. Create the file /etc/init.d/php-fastcgi.

1
sudo vi /etc/init.d/php-fastcgi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#!/bin/bash
 
### BEGIN INIT INFO                                                                                                                     
# Provides:          php-fastcgi                                                                                                        
# Required-Start:    $remote_fs $syslog                                                                                                 
# Required-Stop:     $remote_fs $syslog                                                                                                 
# Default-Start:     2 3 4 5                                                                                                            
# Default-Stop:      0 1 6                                                                                                              
# Short-Description: Start daemon at boot time                                                                                          
# Description:       Enable service provided by daemon.                                                                                 
### END INIT INFO                                                                                                                       
 
 
 
if [ `grep -c "nginx" /etc/passwd` = "1" ]; then 
   FASTCGI_USER=nginx
elif [ `grep -c "www-data" /etc/passwd` = "1" ]; then 
   FASTCGI_USER=www-data
elif [ `grep -c "http" /etc/passwd` = "1" ]; then 
   FASTCGI_USER=http
else 
# Set the FASTCGI_USER variable below to the user that 
# you want to run the php-fastcgi processes as
 
FASTCGI_USER=
fi
 
PHP_SCRIPT=/usr/bin/php-fastcgi
RETVAL=0
case "$1" in
    start)
      sudo -u $FASTCGI_USER $PHP_SCRIPT
      RETVAL=$?
  ;;
    stop)
      killall -9 php5-cgi
      RETVAL=$?
  ;;
    restart)
      killall -9 php5-cgi
      sudo -u $FASTCGI_USER $PHP_SCRIPT
      RETVAL=$?
  ;;
    *)
      echo "Usage: php-fastcgi {start|stop|restart}"
      exit 1
  ;;
esac      
exit $RETVAL

Now we can start the new service

1
sudo /etc/init.d/php-fastcgi start

Then we need to add it to the init scripts so it starts up on reboot.

1
sudo update-rc.d /etc/init.d/php-fastcgi defaults

Now to finalize the nginx configuration. I prefer to use a separate config file per site so I modified the /opt/nginx/conf/nginx.conf file by adding this line.

1
2
3
4
5
6
7
8
9
10
11
12
...
gzip  on;
    gzip_http_version 1.1;
    gzip_disable "MSIE [1-6]\.(?!.*SV1)";
    gzip_types text/plain text/css
               application/x-javascript text/xml
               application/xml application/xml+rss
               text/javascript;
include /opt/etc/nginx/sites-enabled/*;
 
server {
...

In addition I turned the gzip on, this is a test server so you may want to keep it off, but I prefer to keep my test server as close to production as I can. Next I create a config based on the site. Lets pretend it’s this site adamruggles.net.

1
sudo vi /opt/etc/nginx/sites-available/adamruggles.net.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
server {
    listen   80;
    server_name test.adamruggles.net www.adamruggles.net adamruggles.net;
    access_log /srv/adamruggles.net/logs/access.log;
    error_log /srv/adamruggles.net/logs/error.log;
    root /srv/adamruggles.net/public;    
 
    location / {
        try_files $uri $uri/ /index.php?$args;
        index  index.html index.htm index.php;
    }
 
    rewrite /wp-admin$ $scheme://$host$uri/ permanent;
 
    location ~ \.php$ {
        try_files $uri = 404;
        fastcgi_split_path_info ^(.+.php)(.*)$;
        fastcgi_pass unix:/var/run/php-fastcgi/php-fastcgi.socket;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME /srv/adamruggles.net/public$fastcgi_script_name;
        include /opt/nginx/conf/fastcgi_params;
    }

Some parts of this configuration are specifically for WordPress. If you’re not using WordPress the you can modify the rewrite rule and the location / try files. Review nginx’s documentation for your configuration.

Next we create a symlink (this is very similar to how the ubuntu config works and I like it).

1
sudo ln -s /opt/etc/nginx/sites-available/adamruggles.net.conf /opt/etc/nginx/sites-enabled/adamruggles.net.conf

You will obviously want to change all he references to adamruggles.net to whatever domain you’re building your website for. I usually use the ip address for testing servers since they’re virtualized and I haven’t setup DNS for my virtual machines. At this point you can restart nginx so the new configuration takes affect.

1
sudo /etc/init.d/nginx restart

If you encounter any issues following this guide please leave a comment and I’ll address your issue.

Getting JetPack Carousel working with a new Theme

I recently pushed a new theme to my site and noticed that I could no longer use JetPack’s Carousel feature. It was breaking due to a javascript error.

$.browser.mozilla

I did a little research and it appears that the JetPack Carousel is not currently compatible with jQuery 1.9 which removed the browser detection code. If you’re running into this issue you can add it back in using the code here: https://github.com/pupunzi/jquery.mb.browser/archive/master.zip. This is only a work around until JetPack moves to the latest jQuery, but it might help with other WordPress plugins that depend on jQuery’s browser detection.

Here is the code in full incase that link is ever taken down.

/*
 * ******************************************************************************
 *  jquery.mb.components
 *  file: jquery.mb.browser.js
 *
 *  Copyright (c) 2001-2013. Matteo Bicocchi (Pupunzi);
 *  Open lab srl, Firenze - Italy
 *  email: matteo@open-lab.com
 *  site: 	http://pupunzi.com
 *  blog:	http://pupunzi.open-lab.com
 * 	http://open-lab.com
 *
 *  Licences: MIT, GPL
 *  http://www.opensource.org/licenses/mit-license.php
 *  http://www.gnu.org/licenses/gpl.html
 *
 *  last modified: 17/01/13 0.12
 *  *****************************************************************************
 */
 
/*******************************************************************************
 *
 * jquery.mb.browser
 * Author: pupunzi
 * Creation date: 16/01/13
 *
 ******************************************************************************/
/*Browser detection patch*/
 
(function($){
 
	if(jQuery.browser) return;
 
	jQuery.browser = {};
	jQuery.browser.mozilla = false;
	jQuery.browser.webkit = false;
	jQuery.browser.opera = false;
	jQuery.browser.msie = false;
 
	var nAgt = navigator.userAgent;
	jQuery.browser.name  = navigator.appName;
	jQuery.browser.fullVersion  = ''+parseFloat(navigator.appVersion);
	jQuery.browser.majorVersion = parseInt(navigator.appVersion,10);
	var nameOffset,verOffset,ix;
 
// In Opera, the true version is after "Opera" or after "Version"
	if ((verOffset=nAgt.indexOf("Opera"))!=-1) {
		jQuery.browser.opera = true;
		jQuery.browser.name = "Opera";
		jQuery.browser.fullVersion = nAgt.substring(verOffset+6);
		if ((verOffset=nAgt.indexOf("Version"))!=-1)
			jQuery.browser.fullVersion = nAgt.substring(verOffset+8);
	}
// In MSIE, the true version is after "MSIE" in userAgent
	else if ((verOffset=nAgt.indexOf("MSIE"))!=-1) {
		jQuery.browser.msie = true;
		jQuery.browser.name = "Microsoft Internet Explorer";
		jQuery.browser.fullVersion = nAgt.substring(verOffset+5);
	}
// In Chrome, the true version is after "Chrome"
	else if ((verOffset=nAgt.indexOf("Chrome"))!=-1) {
		jQuery.browser.webkit = true;
		jQuery.browser.name = "Chrome";
		jQuery.browser.fullVersion = nAgt.substring(verOffset+7);
	}
// In Safari, the true version is after "Safari" or after "Version"
	else if ((verOffset=nAgt.indexOf("Safari"))!=-1) {
		jQuery.browser.webkit = true;
		jQuery.browser.name = "Safari";
		jQuery.browser.fullVersion = nAgt.substring(verOffset+7);
		if ((verOffset=nAgt.indexOf("Version"))!=-1)
			jQuery.browser.fullVersion = nAgt.substring(verOffset+8);
	}
// In Firefox, the true version is after "Firefox"
	else if ((verOffset=nAgt.indexOf("Firefox"))!=-1) {
		jQuery.browser.mozilla = true;
		jQuery.browser.name = "Firefox";
		jQuery.browser.fullVersion = nAgt.substring(verOffset+8);
	}
// In most other browsers, "name/version" is at the end of userAgent
	else if ( (nameOffset=nAgt.lastIndexOf(' ')+1) <
			(verOffset=nAgt.lastIndexOf('/')) )
	{
		jQuery.browser.name = nAgt.substring(nameOffset,verOffset);
		jQuery.browser.fullVersion = nAgt.substring(verOffset+1);
		if (jQuery.browser.name.toLowerCase()==jQuery.browser.name.toUpperCase()) {
			jQuery.browser.name = navigator.appName;
		}
	}
// trim the fullVersion string at semicolon/space if present
	if ((ix=jQuery.browser.fullVersion.indexOf(";"))!=-1)
		jQuery.browser.fullVersion=jQuery.browser.fullVersion.substring(0,ix);
	if ((ix=jQuery.browser.fullVersion.indexOf(" "))!=-1)
		jQuery.browser.fullVersion=jQuery.browser.fullVersion.substring(0,ix);
 
	jQuery.browser.majorVersion = parseInt(''+jQuery.browser.fullVersion,10);
	if (isNaN(jQuery.browser.majorVersion)) {
		jQuery.browser.fullVersion  = ''+parseFloat(navigator.appVersion);
		jQuery.browser.majorVersion = parseInt(navigator.appVersion,10);
	}
	jQuery.browser.version = jQuery.browser.majorVersion;
})(jQuery)

All credit for this workaround goes to: Pupunzi

Adventures with HTML5 and offline content on an iPad

I’ve been thrown on a project at work where we need to make an app work completely offline.  The catch, the app was written in sencha touch (HTML5) and talkes to a web service for a whole range of content.  Some of those are interactive HTML elements and while others are videos.  Due to a deadline we’re not able to re-architect the project like myself and many on my team would like.

Our first idea was to simply use HTML5 cache to handling the offline bits.  However the data coming from the web services that would need to be cached is on the order of a couple of gigabytes of data.  We wrote up some test code that overrides the handling of Cache (NSURLCache).  This kind of worked but required us downloading content twice and not all AJAX calls were being intercepted by the cache.  We settled on implementing the NSURLProtocol to capture the responses when online and saving them to a special cache folder.

This works by intercepting the users requests when online and saving the response to the file system with the exact path of the request.  Not the best strategy but considering the time constraint the best one available for this application.  This works well for items you’ve already viewed but doesn’t handle the case for locations the user hasn’t browsed yet.  We ended up creating a download gesture that downloads all content needed for offline.  Unfortunately the web services were never designed with this in mind so it takes approximately 1 hour to download all the content.  Obviously it isn’t designed for a user to do, just an admin.

Two major issues with this solution are loading of CSS and HTML5 video.  For some strange reason if you turn the wifi off on the ipad, it no longer returns the correct MIME type for CSS files. The solution was to add an additional check and return the correct mime.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (NSString *)mimeTypeForPath:(NSString *)originalPath
{
    if (![[NSFileManager defaultManager] fileExistsAtPath:originalPath]) {
        return @"";
    }
    // Borrowed from http://stackoverflow.com/questions/5996797/determine-mime-type-of-nsdata-loaded-from-a-file
    // itself, derived from http://stackoverflow.com/questions/2439020/wheres-the-iphone-mime-type-database
    CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (CFStringRef)[originalPath pathExtension], NULL);
    CFStringRef mimeType = UTTypeCopyPreferredTagWithClass(UTI, kUTTagClassMIMEType);
    CFRelease(UTI);
    if (!mimeType) {
        if ([[originalPath pathExtension] isEqualToString:@"css"]) {
            return @"text/css";
        } else {
            return @"application/octet-stream";
        }
    }
    return [NSMakeCollectable((NSString *)mimeType) autorelease];
}

The harder issue proved to be the HTML5 video when offline. For it to play on the device you have to set the appropriate content range and accept headers otherwise it just won’t work. Here is a quick and dirty example that I used to get it working.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
    ...
    NSInteger status = 200;
    NSUInteger contentLength = [content length];
    NSDictionary* headers = [[self request] allHTTPHeaderFields];
    NSMutableDictionary *responseHeaders = [NSMutableDictionary dictionary];
    if ([[headers allKeys] containsObject:@"Range"]) {
        NSArray *parts = [[[headers objectForKey:@"Range"] substringFromIndex:6] componentsSeparatedByString:@"-"];
        NSRange range;
        range.location = [[parts objectAtIndex:0] integerValue];
        range.length = [[parts objectAtIndex:1] integerValue] - [[parts objectAtIndex:0] integerValue] + 1;
        [responseHeaders setObject:[NSString stringWithFormat:@"%d", range.length] forKey:@"Content-Length"];
        [responseHeaders setObject:[NSString stringWithFormat:@"bytes %@-%@/%i",
                                       [parts objectAtIndex:0],
                                       [parts objectAtIndex:1],
                                       contentLength]
                            forKey:@"Content-Range"];
        content = [content subdataWithRange:range];
        status = 206;
    } else {
        [responseHeaders setObject:[NSString stringWithFormat:@"%d", [content length]] forKey:@"Content-Length"];
    }
    [responseHeaders setObject:mime forKey:@"Content-Type"];
    [responseHeaders setObject:@"bytes" forKey:@"Accept-Ranges"];
    [responseHeaders setObject:@"Keep-Alive" forKey:@"Connection"];
    response = [[[NSHTTPURLResponse alloc] initWithURL:[[self request] URL]
                                            statusCode:status
                                           HTTPVersion:@"1.1"
                                          headerFields:responseHeaders] autorelease];
 
    [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
    [[self client] URLProtocol:self didLoadData:content];
    [[self client] URLProtocolDidFinishLoading:self];
	...

So if you’re in a similar boat and needing to get an HTML5 movie to play in a UIWebview this might help.

Update!

It does appear that this only works in the simulator. As of right now the iPad or iPhone does not work with the above code. The NSURLProtocol never event gets called when offline for a video.

Full Text Search

I have been recently tasked with looking into upgrading the libraries we used for full text search on a site that I help maintain.  Search has become a pretty integral part of our site and performance has gotten worse over time.  We currently use Compass to power the full text searches, but it is no longer being maintained.  At the core of compass is Lucene which is a very good search engine.  Because of Compass we’re stuck with an older version of Lucene and so can’t use all the goodies that are in newer releases.

In my search an alternative I’ve reviewed elasticsearch, Solr, Hibernate Search, and using Lucene directly.  I eliminated elasticsearch and Solr based on the integration that is necessary.  While they can do what we need it would be a lot of extra work, more than is necessary to make them work.  So I’ve spent the last couple of days pouring over any documentation on Hibernate Search and Lucene.  I already have a lot of experience with Lucene already and may of our queries already build lucene queries directly and pass it to the Compass APIs.  Hibernate Search has been on my radar for a while but I knew very little about it.

While Hibernate Search is a very good framework, I’m going to end up recommending using Lucene directly.  Hibernate Search is good for applications that want to provide Full Text Search for database applications (sites).  However there are many things that we index that are not stored in any database and do not have entities that represent the searchable data.  In my opinion it would be the same amount of work to make Hibernate Search work with our site as it would to work with Lucene directly.

Now I just need to figure out how we’re going to handle distributing the index between our various nodes.  I’m still researching various strategies, such as HadoopKatta, various strategies using JMS, NFS and more.  Hopefully I’ll have a plan mapped out relatively soon.

Windows Developer Preview 8

I downloaded a copy of the developer preview for windows 8. I decided to load up VMWare and I was greeted with this error: vcpu-0:NOT_IMPLEMENTED vmcore/vmm/intr/apic.c

It appears that VMWare Workstation 7 and VMWare Fusion 3 are not compatible. I downloaded a trial version of VMWare Workstation 8 on my Windows 7 PC and it’s installing. I have to hand it to Microsoft, Windows 8 seems to be genuinely interesting. I’ll post my impressions once I’ve had a chance to play with it some more.

I might try VMWare Fusion 4 trial as well…

Update: For those wanting to try and install Windows 8 preview you will need to select windows 7 (x86 or x86_64) and bypass the easy install.  To by pass the easy install you need to create a new virtual machine and when prompted for the media select “I will install the operating system later.”.  Then just double click on the CD/DVD (IDE) under devices and select the ISO you downloaded.  You can then power on the virtual machine and continue installing.

My Windows Club does a good job of walking your through the process.

Pre-compile JSP pages for Apache Geronimo with Maven

In Apache Geronimo 2.2/w tomcat 6 they don’t appear to be using the tomcat jasper JSP compiler. So to pre-compile JSP pages you will need to modify the dependencies of the jspc maven compiler.

<plugin>
    <groupId>org.codehaus.mojo.jspc</groupId>
    <artifactId>jspc-maven-plugin</artifactId>
    <version>2.0-alpha-3</version>
    <executions>
        <execution>
            <goals>
                <goal>compile</goal>
            </goals>
        </execution>
    </executions>
    <dependencies>
        <dependency>
            <groupId>org.codehaus.mojo.jspc</groupId>
            <artifactId>jspc-compiler-tomcat6</artifactId>
            <version>2.0-alpha-3</version>
            <exclusions>
                <exclusion>
                    <groupId>org.apache.tomcat</groupId>
                    <artifactId>jasper</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.apache.tomcat</groupId>
                    <artifactId>jasper-el</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.apache.tomcat</groupId>
                    <artifactId>jasper-jdt</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.apache.tomcat</groupId>
                    <artifactId>servlet-api</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.apache.tomcat</groupId>
                    <artifactId>jsp-api</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.apache.tomcat</groupId>
                    <artifactId>el-api</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.apache.tomcat</groupId>
                    <artifactId>annotations-api</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.apache.geronimo.ext.tomcat</groupId>
            <artifactId>jasper</artifactId>
            <version>6.0.29.0</version>
        </dependency>
    </dependencies>
    <configuration>
        <inputwebxml>${project.basedir}/target/${project.artifactId}-${project.version}/WEB-INF/web.xml</inputwebxml>
        <sources>
            <directory>${project.basedir}/target/${project.artifactId}-${project.version}</directory>
            <includes>
                <include>**/*.jsp</include>
            </includes>
        </sources>
    </configuration>
</plugin>

Solution to DVFactoryException for wsdl2java

If you’re seeing this error when using cxf-codegen-plugin, there is an easy solution:

org.apache.xerces.impl.dv.DVFactoryException: DTD factory class org.apache.xerces.impl.dv.dtd.DTDDVFactoryImpl does not extend from DTDDVFactory.

<plugin>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-codegen-plugin</artifactId>
    <version>${cxf.version}</version>
    <dependencies>
        <dependency>
            <groupId>xerces</groupId>
            <artifactId>xercesImpl</artifactId>
            <version>2.8.1</version>
        </dependency>
    </dependencies>
    ...
</plugin>

The key part is to add the Xerces dependency. After that you should no longer see that error when you generate Java stubs from the wsdl.