Watermarking Images with Java Extensions

Content and Intellectual Property protection is a vital issue for any web site providing paid-for content. The Login Abuse article describes how ZXTM can be used to detect when a username and password is reused from different locations; this article showcases the power of ZXTM 5.0's Java™ Extensions to apply a dynamically-generated, visible watermark to every image served up by a load-balanced website.

This article covers:

  1. Using the Eclipse IDE to build a Java Extension;
  2. Applying a Java Extension to the traffic managed by a load-balanced service;
  3. Some optimization tricks to control how the extension is invoked;
  4. Debugging Java Extensions and patching the code on the fly.

For more information on Java Extensions, you may like to read the What are Java Extensions article.

Prerequisites

Before you begin, make sure that you have:

  • A working copy of ZXTM 5.0, with the correct Java Runtime Environment (Sun JRE 1.5+) installed on the server;
  • A copy of a Java IDE; this example assumes you are using Eclipse and have downloaded the Sun Java Development Kit (JDK).

If you are using a different Java IDE, or simply building Java classes using the javac command-line compiler, you may find the additional information in the ZXTM Java Development Guide useful.

Configure the ZXTM to load-balance traffic to a suitable website; you can use a public website like www.zeus.com (remember to add a rule to set the host header if necessary). Check you can receive the website content through the ZXTM.

Step 1: Create your Java Extension

Go to the ZXTM 5.0 Admin interface, and locate the Catalog->Java Extensions page. On that page, locate the links to the Java Servlet API and ZXTM Java Extensions API files, and save these two Jar files in a convenient, long-term location:

JAR Download

In Eclipse, create a new project:

  • Project Type: Java Project;
  • Project Name: WaterMark; use the default options;
  • Java Settings: Select the 'Libraries' tar and add the two external Jar files that you stored in the previous step:

Create project in eclipse

Once you've created the project, go to the Package Explorer. Right-click on your project and create a new Class named 'WaterMark' with the default options.

Paste the following code into the WaterMark.java file that is created:

public class WaterMark extends HttpServlet {
    private static final long serialVersionUID = 1L;

    public void doGet( HttpServletRequest req, HttpServletResponse res )
        throws ServletException, IOException
    {
        try {
            ZXTMHttpServletResponse zres = (ZXTMHttpServletResponse) res;

            String ct = zres.getHeader( "Content-Type" );
            if( ct == null || ! ct.startsWith( "image/" ) ) return;

            InputStream is = zres.getInputStream();
            BufferedImage img = ImageIO.read( is );

            Graphics2D g = (Graphics2D)img.getGraphics();
            int width = img.getWidth();
            int height = img.getHeight();

            if( width < 200 || height < 30 ) return;

            String message = "Hello world!";

            g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
                RenderingHints.VALUE_ANTIALIAS_ON);
            g.setComposite( AlphaComposite.getInstance( 
                AlphaComposite.SRC_OVER, (float)0.5 ));

            Font myFont = new Font( "Sans", Font.BOLD, 18 );
            Rectangle2D bb = myFont.getStringBounds( message, 
                g.getFontRenderContext() );

            int x = 2;
            int y = (int)bb.getHeight();

            g.setFont( myFont );
            g.setColor( Color.lightGray );
            g.drawString( message, x, y );

            zres.setHeader( "Content-Type", "image/png" );
            ImageIO.write( img, "PNG", zres.getOutputStream() );
        } catch( Exception e ) {
            log( req.getRequestURI() + ": " + e.toString() );
        }
    }

    public void doPost( HttpServletRequest req, HttpServletResponse res )
        throws ServletException, IOException
    {
        doGet( req, res );
    }
}

Paste this source in and hit ‘Ctrl-Shift-O’ to get the correct imports. Then save the file – this will automatically compile it; check that there were no errors in the compilation.

Step 2: Load the extension into ZXTM and watermark some images

Go to the Java Extensions catalog page and upload the Java Extension 'class' file for the WaterMark extension:

If the java code compiled correctly, you’ll find the class file in:

C:\Documents and Settings\NAME\workspace\WaterMark\bin

When you upload the class file, the ZXTM Admin Server will automatically create a simple RuleBuilder rule that invokes the Java Extension.

Configure your virtual server to run the RuleBuilder rule on each response, then shift-reload the webpage that is delivered through ZXTM to clear your cache and reload each image:

www.zeus.com

Note the little "Hello world!" watermark on the top left of any images larger then 200x30 pixels.

Step 3: Optimize the way that the extension is called

The Java Extension is called by the RuleBuilder rule on every HTTP response. However, the extension only processes images; HTML, CSS and other document types are ignored.

Selectively running Java Extensions

Invoking a Java Extension carries some overhead, so it is prudent to ensure that Extensions are only invoked when they are needed. With a small change to the rule, you can ensure that this is the case.

First, convert the "WaterMark" RuleBuilder rule to TrafficScript by editing the rule and using the "Convert Rule" button. This will create a simple TrafficScript rule which calls the WaterMark extension:

java.run( "WaterMark" );

Edit the rule to add a condition that only runs the WaterMark extension when the object type is an image:

$contenttype = http.getResponseHeader( "Content-Type" );

if( string.startsWith( $contenttype, "image/" ) ) {
    java.run( "WaterMark" );
}

Passing parameters to a Java Extension

You can pass parameters from TrafficScript to a Java Extension. They are passed in as additional arguments to the ‘java.run()’ TrafficScript function:

$ip = request.getRemoteIP();
$time = sys.timeToString( sys.time() );

$message = "IP: ".$ip.", ".$time;

$contenttype = http.getResponseHeader( "Content-Type" );

if( string.startsWith( $contenttype, "image/" ) ) {
    java.run( "WaterMark", $message );
}

The Java extension can read these arguments using the 'args' attribute, which returns a string array of the argument values. Change the Extension code as follows:

Originally:

String message = "Hello world!";

Change to:

String[] args = (String[])req.getAttribute( "args" );
String message = ( args != null ) ? args[0] : "Hello world!";

In Eclipse, you will need to re-save the source code after editing it; that will immediately compile it and update the WaterMark.class file on disk if the compilation was successful. Check for any error messages in the Eclipse editor that refer to the WaterMark.java compilation.

Use the ZXTM Admin Interface to load in the new copy of the Java extension.

Now, when you shift-reload the web page (to clear the cache) the watermark text on the image will contain the message created in the TrafficScript rule, with the IP address of the remote
user and the time when the image was downloaded.

Of course, you could also generate this message directly in the Java Extension, but quick code changes (such as modifying the text in the message) are easier when the code resides in TrafficScript rather than a compiled Java class.

Step 4: Live debugging and hot code patching

Finally, refer to the "Remote Debugging" section of the ZXTM 5.0 Java Development Guide.
This describes how to configure the arguments used to start the Java Virtual Machine (JVM) so that it can accept live debugging sessions from a remote debugger, and how to configure Eclipse to connect to the JVM.

You can edit and save the code in Eclipse. When you save the code, Eclipse compiles it and patches the code in the JVM on the fly. Try changing the point size of the font or the color and see the effects immediately:

Font myFont = new Font( "Serif", Font.BOLD, 12 );

and...

g.setColor( Color.red );

Live patching in Eclipse is a great way to debug, test and update code, but remember that it only patches the code in the live Java VM. When ZXTM restarts, it will fall back to the version of the Java class that you originally uploaded, so once you’re finished, remember to upload the compiled class through the Admin interface so that it persists.

Read more...

Browse the Knowledgehub for more examples on ZXTM 5.0's Java Extensions.

Owen Garrett [Zeus Dev Team] 22 May 2008  Permalink  
Leave a comment ...
Your email address will not be displayed.
Your URL will be displayed.
This public messageboard is not a forum for technical support. To report technical support problems, please contact our dedicated Support team using the instructions at the bottom of this page.
Options:
 
(Line breaks become <br />)
(Set cookies for name, email & url)
Download Free ZXTM Desktop Edition

Recent Articles

Other Resources



www.zeus.com