All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jnlp.sample.servlet.JnlpFileHandler Maven / Gradle / Ivy

Go to download

JNLP Sample servlet that supports pack200 protocol. Taken from Sun's JDK sample/jnlp directory thanks to its permissive license.

The newest version!
/*
 * @(#)JnlpFileHandler.java	1.12 05/11/17
 * 
 * Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * -Redistribution of source code must retain the above copyright notice, this
 *  list of conditions and the following disclaimer.
 *
 * -Redistribution in binary form must reproduce the above copyright notice,
 *  this list of conditions and the following disclaimer in the documentation
 *  and/or other materials provided with the distribution.
 *
 * Neither the name of Sun Microsystems, Inc. or the names of contributors may
 * be used to endorse or promote products derived from this software without
 * specific prior written permission.
 *
 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
 * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
 * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN")
 * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
 * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
 * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
 * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
 * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
 * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 *
 * You acknowledge that this software is not designed, licensed or intended
 * for use in the design, construction, operation or maintenance of any
 * nuclear facility.
 */

package jnlp.sample.servlet;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpUtils;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.TimeZone;

/* The JNLP file handler implements a class that keeps
 * track of JNLP files and their specializations
 */
public class JnlpFileHandler
{
    private static final String JNLP_MIME_TYPE = "application/x-java-jnlp-file";

    private static final String HEADER_LASTMOD = "Last-Modified";

    private ServletContext _servletContext;

    private Logger _log = null;

    private HashMap _jnlpFiles = null;

    /**
     * Initialize JnlpFileHandler for the specific ServletContext
     */
    public JnlpFileHandler( ServletContext servletContext, Logger log )
    {
        _servletContext = servletContext;
        _log = log;
        _jnlpFiles = new HashMap();
    }

    private static class JnlpFileEntry
    {
        // Response
        DownloadResponse _response;

        // Keeps track of cache is out of date
        private long _lastModified;

        // Constructor
        JnlpFileEntry( DownloadResponse response, long lastmodfied )
        {
            _response = response;
            _lastModified = lastmodfied;
        }

        public DownloadResponse getResponse()
        {
            return _response;
        }

        long getLastModified()
        {
            return _lastModified;
        }
    }

    /* Main method to lookup an entry */
    public synchronized DownloadResponse getJnlpFile( JnlpResource jnlpres, DownloadRequest dreq )
        throws IOException
    {
        String path = jnlpres.getPath();
        URL resource = jnlpres.getResource();
        long lastModified = jnlpres.getLastModified();

        _log.addDebug( "lastModified: " + lastModified + " " + new Date( lastModified ) );
        if ( lastModified == 0 )
        {
            _log.addWarning( "servlet.log.warning.nolastmodified", path );
        }

        // fix for 4474854:  use the request URL as key to look up jnlp file
        // in hash map
        String reqUrl = HttpUtils.getRequestURL( dreq.getHttpRequest() ).toString();

        // Check if entry already exist in HashMap
        JnlpFileEntry jnlpFile = (JnlpFileEntry) _jnlpFiles.get( reqUrl );

        if ( jnlpFile != null && jnlpFile.getLastModified() == lastModified )
        {
            // Entry found in cache, so return it
            return jnlpFile.getResponse();
        }

        // Read information from WAR file
        long timeStamp = lastModified;
        String mimeType = _servletContext.getMimeType( path );
        if ( mimeType == null )
        {
            mimeType = JNLP_MIME_TYPE;
        }

        StringBuffer jnlpFileTemplate = new StringBuffer();
        URLConnection conn = resource.openConnection();
        BufferedReader br = new BufferedReader( new InputStreamReader( conn.getInputStream(), "UTF-8" ) );
        String line = br.readLine();
        if ( line != null && line.startsWith( "TS:" ) )
        {
            timeStamp = parseTimeStamp( line.substring( 3 ) );
            _log.addDebug( "Timestamp: " + timeStamp + " " + new Date( timeStamp ) );
            if ( timeStamp == 0 )
            {
                _log.addWarning( "servlet.log.warning.notimestamp", path );
                timeStamp = lastModified;
            }
            line = br.readLine();
        }
        while ( line != null )
        {
            jnlpFileTemplate.append( line );
            line = br.readLine();
        }

        String jnlpFileContent = specializeJnlpTemplate( dreq.getHttpRequest(), path, jnlpFileTemplate.toString() );

        // Convert to bytes as a UTF-8 encoding
        byte[] byteContent = jnlpFileContent.getBytes( "UTF-8" );

        // Create entry
        DownloadResponse resp =
            DownloadResponse.getFileDownloadResponse( byteContent, mimeType, timeStamp, jnlpres.getReturnVersionId() );
        jnlpFile = new JnlpFileEntry( resp, lastModified );
        _jnlpFiles.put( reqUrl, jnlpFile );

        return resp;
    }

    /* Main method to lookup an entry (NEW for JavaWebStart 1.5+) */
    public synchronized DownloadResponse getJnlpFileEx( JnlpResource jnlpres, DownloadRequest dreq )
        throws IOException
    {
        String path = jnlpres.getPath();
        URL resource = jnlpres.getResource();
        long lastModified = jnlpres.getLastModified();

        _log.addDebug( "lastModified: " + lastModified + " " + new Date( lastModified ) );
        if ( lastModified == 0 )
        {
            _log.addWarning( "servlet.log.warning.nolastmodified", path );
        }

        // fix for 4474854:  use the request URL as key to look up jnlp file
        // in hash map
        String reqUrl = HttpUtils.getRequestURL( dreq.getHttpRequest() ).toString();
        // SQE: To support query string, we changed the hash key from Request URL to (Request URL + query string)
        if ( dreq.getQuery() != null )
        {
            reqUrl += dreq.getQuery();
        }

        // Check if entry already exist in HashMap
        JnlpFileEntry jnlpFile = (JnlpFileEntry) _jnlpFiles.get( reqUrl );

        if ( jnlpFile != null && jnlpFile.getLastModified() == lastModified )
        {
            // Entry found in cache, so return it
            return jnlpFile.getResponse();
        }

        // Read information from WAR file
        long timeStamp = lastModified;
        String mimeType = _servletContext.getMimeType( path );
        if ( mimeType == null )
        {
            mimeType = JNLP_MIME_TYPE;
        }

        StringBuffer jnlpFileTemplate = new StringBuffer();
        URLConnection conn = resource.openConnection();
        BufferedReader br = new BufferedReader( new InputStreamReader( conn.getInputStream(), "UTF-8" ) );
        String line = br.readLine();
        if ( line != null && line.startsWith( "TS:" ) )
        {
            timeStamp = parseTimeStamp( line.substring( 3 ) );
            _log.addDebug( "Timestamp: " + timeStamp + " " + new Date( timeStamp ) );
            if ( timeStamp == 0 )
            {
                _log.addWarning( "servlet.log.warning.notimestamp", path );
                timeStamp = lastModified;
            }
            line = br.readLine();
        }
        while ( line != null )
        {
            jnlpFileTemplate.append( line );
            line = br.readLine();
        }

        String jnlpFileContent = specializeJnlpTemplate( dreq.getHttpRequest(), path, jnlpFileTemplate.toString() );

        /* SQE: We need to add query string back to href in jnlp file. We also need to handle JRE requirement for
       * the test. We reconstruct the xml DOM object, modify the value, then regenerate the jnlpFileContent.
       */
        String query = dreq.getQuery();
        String testJRE = dreq.getTestJRE();
        _log.addDebug( "Double check query string: " + query );
        // For backward compatibility: Always check if the href value exists.
        // Bug 4939273: We will retain the jnlp template structure and will NOT add href value. Above old
        // approach to always check href value caused some test case not run.
        if ( query != null )
        {
            byte[] cb = jnlpFileContent.getBytes( "UTF-8" );
            ByteArrayInputStream bis = new ByteArrayInputStream( cb );
            try
            {
                DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
                DocumentBuilder builder = factory.newDocumentBuilder();
                Document document = builder.parse( bis );
                if ( document != null && document.getNodeType() == Node.DOCUMENT_NODE )
                {
                    boolean modified = false;
                    Element root = document.getDocumentElement();

                    if ( root.hasAttribute( "href" ) && query != null )
                    {
                        String href = root.getAttribute( "href" );
                        root.setAttribute( "href", href + "?" + query );
                        modified = true;
                    }
                    // Update version value for j2se tag
                    if ( testJRE != null )
                    {
                        NodeList j2seNL = root.getElementsByTagName( "j2se" );
                        if ( j2seNL != null )
                        {
                            Element j2se = (Element) j2seNL.item( 0 );
                            String ver = j2se.getAttribute( "version" );
                            if ( ver.length() > 0 )
                            {
                                j2se.setAttribute( "version", testJRE );
                                modified = true;
                            }
                        }
                    }
                    TransformerFactory tFactory = TransformerFactory.newInstance();
                    Transformer transformer = tFactory.newTransformer();
                    DOMSource source = new DOMSource( document );
                    StringWriter sw = new StringWriter();
                    StreamResult result = new StreamResult( sw );
                    transformer.transform( source, result );
                    jnlpFileContent = sw.toString();
                    _log.addDebug( "Converted jnlpFileContent: " + jnlpFileContent );
                    // Since we modified the file on the fly, we always update the timestamp value with current time
                    if ( modified )
                    {
                        timeStamp = new java.util.Date().getTime();
                        _log.addDebug( "Last modified on the fly:  " + timeStamp );
                    }
                }
            }
            catch ( Exception e )
            {
                _log.addDebug( e.toString(), e );
            }
        }

        // Convert to bytes as a UTF-8 encoding
        byte[] byteContent = jnlpFileContent.getBytes( "UTF-8" );

        // Create entry
        DownloadResponse resp =
            DownloadResponse.getFileDownloadResponse( byteContent, mimeType, timeStamp, jnlpres.getReturnVersionId() );
        jnlpFile = new JnlpFileEntry( resp, lastModified );
        _jnlpFiles.put( reqUrl, jnlpFile );

        return resp;
    }

    /* This method performs the following substituations
    *  $$name
    *  $$codebase
    *  $$context
    */
    private String specializeJnlpTemplate( HttpServletRequest request, String respath, String jnlpTemplate )
    {
        String urlprefix = getUrlPrefix( request );
        int idx = respath.lastIndexOf( '/' ); //
        String name = respath.substring( idx + 1 );    // Exclude /
        String codebase = respath.substring( 0, idx + 1 ); // Include /
        jnlpTemplate = substitute( jnlpTemplate, "$$name", name );
        // fix for 5039951: Add $$hostname macro
        jnlpTemplate = substitute( jnlpTemplate, "$$hostname", request.getServerName() );
        jnlpTemplate = substitute( jnlpTemplate, "$$codebase", urlprefix + request.getContextPath() + codebase );
        jnlpTemplate = substitute( jnlpTemplate, "$$context", urlprefix + request.getContextPath() );
        // fix for 6256326: add $$site macro to sample jnlp servlet
        jnlpTemplate = substitute( jnlpTemplate, "$$site", urlprefix );
        return jnlpTemplate;
    }

    // This code is heavily inspired by the stuff in HttpUtils.getRequestURL
    private String getUrlPrefix( HttpServletRequest req )
    {
        StringBuffer url = new StringBuffer();
        String scheme = req.getScheme();
        int port = req.getServerPort();
        url.append( scheme );        // http, https
        url.append( "://" );
        url.append( req.getServerName() );
        if ( ( scheme.equals( "http" ) && port != 80 ) || ( scheme.equals( "https" ) && port != 443 ) )
        {
            url.append( ':' );
            url.append( req.getServerPort() );
        }
        return url.toString();
    }

    private String substitute( String target, String key, String value )
    {
        int start = 0;
        do
        {
            int idx = target.indexOf( key, start );
            if ( idx == -1 )
            {
                return target;
            }
            target = target.substring( 0, idx ) + value + target.substring( idx + key.length() );
            start = idx + value.length();
        }
        while ( true );
    }

    /**
     * Parses a ISO 8601 Timestamp. The format of the timestamp is:
     * 

* YYYY-MM-DD hh:mm:ss or YYYYMMDDhhmmss *

* Hours (hh) is in 24h format. ss are optional. Time are by default relative * to the current timezone. Timezone information can be specified * by: *

* - Appending a 'Z', e.g., 2001-12-19 12:00Z * - Appending +hh:mm, +hhmm, +hh, -hh:mm -hhmm, -hh to * indicate that the locale timezone used is either the specified * amound before or after GMT. For example, *

* 12:00Z = 13:00+1:00 = 0700-0500 *

* The method returns 0 if it cannot pass the string. Otherwise, it is * the number of milliseconds size sometime in 1969. */ private long parseTimeStamp( String timestamp ) { int YYYY = 0; int MM = 0; int DD = 0; int hh = 0; int mm = 0; int ss = 0; timestamp = timestamp.trim(); try { // Check what format is used if ( matchPattern( "####-##-## ##:##", timestamp ) ) { YYYY = getIntValue( timestamp, 0, 4 ); MM = getIntValue( timestamp, 5, 7 ); DD = getIntValue( timestamp, 8, 10 ); hh = getIntValue( timestamp, 11, 13 ); mm = getIntValue( timestamp, 14, 16 ); timestamp = timestamp.substring( 16 ); if ( matchPattern( ":##", timestamp ) ) { ss = getIntValue( timestamp, 1, 3 ); timestamp = timestamp.substring( 3 ); } } else if ( matchPattern( "############", timestamp ) ) { YYYY = getIntValue( timestamp, 0, 4 ); MM = getIntValue( timestamp, 4, 6 ); DD = getIntValue( timestamp, 6, 8 ); hh = getIntValue( timestamp, 8, 10 ); mm = getIntValue( timestamp, 10, 12 ); timestamp = timestamp.substring( 12 ); if ( matchPattern( "##", timestamp ) ) { ss = getIntValue( timestamp, 0, 2 ); timestamp = timestamp.substring( 2 ); } } else { // Unknown format return 0; } } catch ( NumberFormatException e ) { // Bad number return 0; } String timezone = null; // Remove timezone information timestamp = timestamp.trim(); if ( timestamp.equalsIgnoreCase( "Z" ) ) { timezone = "GMT"; } else if ( timestamp.startsWith( "+" ) || timestamp.startsWith( "-" ) ) { timezone = "GMT" + timestamp; } if ( timezone == null ) { // Date is relative to current locale Calendar cal = Calendar.getInstance(); cal.set( YYYY, MM - 1, DD, hh, mm, ss ); return cal.getTime().getTime(); } else { // Date is relative to a timezone Calendar cal = Calendar.getInstance( TimeZone.getTimeZone( timezone ) ); cal.set( YYYY, MM - 1, DD, hh, mm, ss ); return cal.getTime().getTime(); } } private int getIntValue( String key, int start, int end ) { return Integer.parseInt( key.substring( start, end ) ); } private boolean matchPattern( String pattern, String key ) { // Key must be longer than pattern if ( key.length() < pattern.length() ) { return false; } for ( int i = 0; i < pattern.length(); i++ ) { char format = pattern.charAt( i ); char ch = key.charAt( i ); if ( !( ( format == '#' && Character.isDigit( ch ) ) || ( format == ch ) ) ) { return false; } } return true; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy