view common/src/main/java/digilib/conf/DigilibRequest.java @ 1696:43d48324dceb

Cleanup: fix deprecation warnings for Java 10+. Change "new Integer/Float...(x)" to Integer.valueOf(x). Change clazz.newInstance to clazz.getConstructor().newInstance().
author Robert Casties <casties@mpiwg-berlin.mpg.de>
date Sun, 16 Dec 2018 15:08:01 +0100
parents 3972535c1146
children
line wrap: on
line source

package digilib.conf;

/*
 * #%L
 * DigilibRequest.java
 *
 * lightweight class carrying all parameters for a request to digilib
 * %%
 * Copyright (C) 2002 - 2013 MPIWG Berlin, WTWG Uni Bern
 *                           
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as 
 * published by the Free Software Foundation, either version 3 of the 
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public 
 * License along with this program.  If not, see
 * <http://www.gnu.org/licenses/lgpl-3.0.html>.
 * #L%
 * Authors: Robert Casties (robcast@berlios.de),
 *          Christian Luginbuehl
 *          
 * Created on 27. August 2002
 */

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.StringTokenizer;

import org.apache.log4j.Logger;

import digilib.image.ImageJobDescription;
import digilib.io.FileOps;
import digilib.util.OptionsSet;
import digilib.util.Parameter;
import digilib.util.ParameterMap;

/**
 * Class holding the parameters of a digilib user request. The parameters are
 * mostly named like the servlet parameters: <br>
 * request.path: url of the page/document. <br>
 * fn: url of the page/document. <br>
 * pn: page number. <br>
 * dw: width of result window in pixels. <br>
 * dh: height of result window in pixels. <br>
 * wx: left edge of image area (float from 0 to 1). <br>
 * wy: top edge of image area (float from 0 to 1). <br>
 * ww: width of image area(float from 0 to 1). <br>
 * wh: height of image area(float from 0 to 1). <br>
 * ws: scale factor. <br>
 * mo: special options like 'fit'. <br>
 * ...et cetera
 * 
 * @author casties
 * 
 */
public class DigilibRequest extends ParameterMap {

    private static Logger logger = Logger.getLogger("digilib.request");
    
    /**
     * special options for parsing the request. 
     */
    public static enum ParsingOption {
    	omitIiifImageApi
    }

    /** active pasing options */
    public EnumSet<ParsingOption> parsingOptions = EnumSet.noneOf(ParsingOption.class);
    
    /** IIIF path prefix (taken from config) */
    protected String iiifPrefix = "IIIF";
    
    /** IIIF slash replacement (taken from config) */
    protected String iiifSlashReplacement = null;
    
    /** parse IIIF path as IIIF image API */
    public boolean parseIiifImageApi = true;
    
    /** error message while configuring */
    public String errorMessage = null;

    /** ImageJobDescription for this request */
    protected ImageJobDescription ticket;

    /** DigilibConfiguration for this request */
    protected DigilibConfiguration config;

    public DigilibRequest() {
        super(30);
        initParams();
    }

    /**
     * Create DigilibRequest with DigilibConfiguration.
     * 
     * @param config
     */
    public DigilibRequest(DigilibConfiguration config) {
        super(30);
        this.config = config;
        initParams();
    }

    /**
     * Define and set up parameters with default values.
     */
    protected void initParams() {
        /*
         * Definition of parameters and default values. Parameter of type 's'
         * are for the servlet.
         */

        // url of the page/document (second part)
        newParameter("fn", "", null, 's');
        // page number
        newParameter("pn", Integer.valueOf(1), null, 's');
        // width of client in pixels
        newParameter("dw", Integer.valueOf(0), null, 's');
        // height of client in pixels
        newParameter("dh", Integer.valueOf(0), null, 's');
        // left edge of image (float from 0 to 1)
        newParameter("wx", Float.valueOf(0), null, 's');
        // top edge in image (float from 0 to 1)
        newParameter("wy", Float.valueOf(0), null, 's');
        // width of image (float from 0 to 1)
        newParameter("ww", Float.valueOf(1), null, 's');
        // height of image (float from 0 to 1)
        newParameter("wh", Float.valueOf(1), null, 's');
        // scale factor
        newParameter("ws", Float.valueOf(1), null, 's');
        // special options like 'fit' for gifs
        newParameter("mo", this.options, null, 's');
        // rotation angle (degree)
        newParameter("rot", Float.valueOf(0), null, 's');
        // contrast enhancement factor
        newParameter("cont", Float.valueOf(0), null, 's');
        // brightness enhancement factor
        newParameter("brgt", Float.valueOf(0), null, 's');
        // color multiplicative factors
        newParameter("rgbm", "0/0/0", null, 's');
        // color additive factors
        newParameter("rgba", "0/0/0", null, 's');
        // display dpi resolution (total)
        newParameter("ddpi", Float.valueOf(0), null, 's');
        // display dpi X resolution
        newParameter("ddpix", Float.valueOf(0), null, 's');
        // display dpi Y resolution
        newParameter("ddpiy", Float.valueOf(0), null, 's');
        // scale factor for mo=ascale
        newParameter("scale", Float.valueOf(1), null, 's');
        // color conversion operation
        newParameter("colop", "", null, 's');

        /*
         * Parameters of type 'i' are not exchanged between client and server,
         * but are for the servlets or JSPs internal use.
         */

        // url of the page/document (first part, may be empty)
        newParameter("request.path", "", null, 'i');
        // base URL (from http:// to below /servlet)
        newParameter("base.url", null, null, 'i');
        // elements of IIIF API path
        newParameter("request.iiif.elements", null, null, 'i');
        
        /*
         * Parameters of type 'c' are for the clients use
         */

        // "real" filename
        newParameter("img.fn", "", null, 'c');
        // image dpi x
        newParameter("img.dpix", Integer.valueOf(0), null, 'c');
        // image dpi y
        newParameter("img.dpiy", Integer.valueOf(0), null, 'c');
        // hires image size x
        newParameter("img.pix_x", Integer.valueOf(0), null, 'c');
        // hires image size y
        newParameter("img.pix_y", Integer.valueOf(0), null, 'c');

        /*
         * set local variables from config
         */
        if (config != null) {
            iiifPrefix = config.getAsString("iiif-prefix");
            iiifSlashReplacement = config.getAsString("iiif-slash-replacement");
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see digilib.servlet.ParameterMap#initOptions()
     */
    @Override
    protected void initOptions() {
        options = (OptionsSet) getValue("mo");
    }

    /**
     * Return the request parameters as a String in the parameter form
     * 'fn=/icons&amp;pn=1'. Empty (undefined) fields are not included.
     * 
     * @return String of request parameters in parameter form.
     */
    public String getAsString() {
        return getAsString(0);
    }

    /**
     * Return the request parameters of a given type type as a String in the
     * parameter form 'fn=/icons&amp;pn=1'. Empty (undefined) fields are not
     * included.
     * 
     * @return String of request parameters in parameter form.
     */
    public String getAsString(int type) {
        StringBuffer s = new StringBuffer(50);
        // go through all values
        for (Parameter p : params.values()) {
            if ((type > 0) && (p.getType() != type)) {
                // skip the wrong types
                continue;
            }
            String name = p.getName();
            /*
             * handling special cases
             */
            // request_path adds to fn
            if (name.equals("fn")) {
                s.append("&fn=" + getAsString("request.path") + getAsString("fn"));
                continue;
            }
            /*
             * the rest is sent with its name
             */
            // parameters that are not set or internal are not sent
            if ((!p.hasValue()) || (p.getType() == 'i')) {
                continue;
            }
            s.append("&" + name + "=" + p.getAsString());
        }
        // kill first "&"
        s.deleteCharAt(0);
        return s.toString();
    }

    /**
     * Set request parameters from query string. Uses the separator string qs to
     * get 'fn=foo' style parameters.
     * 
     * @param qs
     *            query string
     * @param sep
     *            parameter-separator string
     */
    public void setWithParamString(String qs, String sep) {
        // go through all request parameters
        String[] qa = qs.split(sep);
        for (int i = 0; i < qa.length; i++) {
            // split names and values on "="
            String[] nv = qa[i].split("=");
            try {
                String name = URLDecoder.decode(nv[0], "UTF-8");
                String val = URLDecoder.decode(nv[1], "UTF-8");
                // is this a known parameter?
                if (params.containsKey(name)) {
                    Parameter p = (Parameter) this.get(name);
                    // internal parameters are not set
                    if (p.getType() == 'i') {
                        continue;
                    }
                    p.setValueFromString(val);
                    continue;
                }
                // unknown parameters are just added with type 'r'
                newParameter(name, null, val, 'r');
            } catch (UnsupportedEncodingException e) {
                // this shouldn't happen anyway
                e.printStackTrace();
            }
        }
    }

    /**
     * Populate a request from a string with an IIIF image API path.
     * 
     * path should be non-URL-decoded and have no leading slash.
     * 
     * URI template:
     * {scheme}://{server}{/prefix}/{identifier}/{region}/{size}/{rotation}/{quality}.{format}
     * 
     * @param path
     *            String with IIIF Image API path.
     * 
     * @see <a href="http://iiif.io/api/image/2.0/">IIIF Image API</a>
     */
    public boolean setWithIiifPath(String path) {
        if (path == null) {
            return false;
        }
        
        List<String> params = new ArrayList<String>(5);
        setValue("request.iiif.elements", params);
        
        // enable passing of delimiter to get empty parameters
        StringTokenizer query = new StringTokenizer(path, "/", true);
        String token;
        /*
         * first parameter prefix
         */
        if (query.hasMoreTokens()) {
            token = getNextDecodedToken(query);
            if (!token.equals(iiifPrefix)) {
                errorMessage = "IIIF path doesn't start with prefix!";
                logger.error(errorMessage);
                return false;
            }
            // skip /
            if (query.hasMoreTokens()) {
                query.nextToken();
            }
        }
        /*
         * following parameters
         */
        while (query.hasMoreTokens()) {
            token = getNextDecodedToken(query);
            if (!token.equals("/")) {
            	params.add(token);
                // skip /
                if (query.hasMoreTokens()) {
                    query.nextToken();
                }
            } else {
            	// empty parameter
            	params.add(null);
            }
        }

        if (parsingOptions.contains(ParsingOption.omitIiifImageApi)) {
        	return true;
        }
        
        /*
         * parse sequence of parameters as IIIF image API
         */
        String identifier = ""; // empty name means image root directory
        String region = null;
        String size = null;
        String rotation = null;
        String quality = null;
        String format = null;

		if (params.size() > 0) {
			/*
			 * first parameter identifier (encoded)
			 */
			identifier = params.get(0);
			
			if (params.size() > 1) {
				/*
				 * second parameter region
				 */
				region = params.get(1);
				
				if (params.size() > 2) {
					/*
					 * third parameter size
					 */
					size = params.get(2);
					
					if (params.size() > 3) {
						/*
						 * fourth parameter rotation
						 */
						rotation = params.get(3);
						
						if (params.size() > 4) {
							/*
							 * fifth parameter quality.format
							 */
							String qf = params.get(4);
							if (qf != null) {
								// quality.format -- color depth and output
								// format
								try {
									String[] parms = qf.split("\\.");
									// quality param
									quality = parms[0];
									// format param
									if (parms.length > 1) {
										format = parms[1];
									}
								} catch (Exception e) {
									errorMessage = "Error parsing quality and format parameters in IIIF path!";
									logger.error(errorMessage, e);
									return false;
								}
							}
						}
					}
				}
			}
		}
        // set request with these parameters
        return setWithIiifImageParams(identifier, region, size, rotation, quality, format);
    }

    private String getNextDecodedToken(StringTokenizer tokens) {
        String token = tokens.nextToken();
        try {
            token = URLDecoder.decode(token, "UTF-8");
            return token;
        } catch (UnsupportedEncodingException e) {
            // this shouldn't happen
        }
        return null;
    }

	/**
	 * Populate a request from IIIF image API parameters.
	 *
	 * @see <a href="http://iiif.io/api/image/2.0/">IIIF Image API</a>
	 * 
	 * @param identifier
	 * @param region
	 * @param size
	 * @param rotation
	 * @param quality
	 * @param format
	 * @return
	 */
	public boolean setWithIiifImageParams(String identifier, String region, String size, 
			String rotation, String quality, String format) {
        // alway set HTTP status code error reporting
        options.setOption(DigilibOption.errcode);
        
        /*
         * parameter identifier (encoded)
         */
        if (identifier != null) {
            try {
                identifier = decodeIiifIdentifier(identifier);
                setValueFromString("fn", identifier);
            } catch (UnsupportedEncodingException e) {
                errorMessage = "Error decoding identifier in IIIF path!";
                logger.error(errorMessage);
                return false;
            }
        } else {
            errorMessage = "Missing identifier in IIIF path!";
            logger.error(errorMessage);
            return false;
        }

        /*
         * parameter region
         */
        if (region != null) {
            if (region.equals("info.json")) {
                // info request
                options.setOption(DigilibOption.info);
                return true;
                
            } else if (region.equals("full")) {
                // full image -- digilib default
            	
            } else if (region.equals("square")) {
                // "squared" crop of full image (square of shortest side length)
            	options.setOption(DigilibOption.sqarea);
            	
            } else if (region.startsWith("pct:")) {
                // pct:x,y,w,h -- region in % of original image
                String[] parms = region.substring(4).split(",");
                try {
                    float x = Float.parseFloat(parms[0]);
                    setValue("wx", x / 100f);
                    float y = Float.parseFloat(parms[1]);
                    setValue("wy", y / 100f);
                    float w = Float.parseFloat(parms[2]);
                    setValue("ww", w / 100f);
                    float h = Float.parseFloat(parms[3]);
                    setValue("wh", h / 100f);
                } catch (Exception e) {
                    errorMessage = "Error parsing range parameter in IIIF path! ";
                    logger.error(errorMessage+e);
                    return false;
                }
            } else {
                // x,y,w,h -- region in pixel of original image :-(
                String[] parms = region.split(",");
                if (parms.length != 4) {
                    errorMessage = "Error parsing range parameter in IIIF path!";
                    logger.error(errorMessage);
                    return false;
                } else {
                    options.setOption(DigilibOption.pxarea);
                    setValueFromString("wx", parms[0]);
                    setValueFromString("wy", parms[1]);
                    setValueFromString("ww", parms[2]);
                    setValueFromString("wh", parms[3]);
                }
            }
        } else {
            // region omitted -- redirect to info request
            options.setOption(DigilibOption.redirect_info);
            return true;
        }
        
        /*
         * parameter size
         */
        if (size != null) {
            if (size.equals("full")) {
                /*
                 * full -- size of original
                 */
                options.setOption(DigilibOption.ascale);
                setValue("scale", 1f);
                
            } else if (size.equals("max")) {
                /*
                 * max -- size of original unless constrained by max image size or area
                 */
                options.setOption(DigilibOption.ascale);
                setValue("scale", 1f);
                // TODO: check with max image size
                
            } else if (size.startsWith("pct:")) {
                /*
                 * pct:n -- n% size of original
                 */
                try {
                    float pct = Float.parseFloat(size.substring(4));
                    options.setOption(DigilibOption.ascale);
                    setValue("scale", pct / 100);
                } catch (NumberFormatException e) {
                    errorMessage = "Error parsing size parameter in IIIF path! ";
                    logger.error(errorMessage+e);
                    return false;
                }
                
            } else {
                /*
                 * w,h -- pixel size
                 */
                try {
                    String[] parms = size.split(",", 2);
                    if (parms[0].length() > 0) {
                        // width param
                        if (parms[0].startsWith("!")) {
                            // !w,h width (in digilib-like bounding box)
                            setValueFromString("dw", parms[0].substring(1));
                        } else if (parms[1].length() == 0) {
                            // w, width only
                            setValueFromString("dw", parms[0]);
                        } else {
                            // w,h -- according to spec, we should distort the image to match ;-(
                        	options.setOption(DigilibOption.squeeze);
                            setValueFromString("dw", parms[0]);
                        }
                    }
                    if (parms[1].length() > 0) {
                        // height param
                        setValueFromString("dh", parms[1]);
                    }
                } catch (Exception e) {
                    errorMessage = "Error parsing size parameter in IIIF path! ";
                    logger.error(errorMessage+e);
                    return false;
                }
            }
        } else {
            // size omitted -- assume "full"
            options.setOption(DigilibOption.ascale);
            setValue("scale", 1f);
            return true;
        }
        
        /*
         * parameter rotation
         */
        if (rotation != null) {
            if (rotation.startsWith("!")) {
                // !n -- mirror and rotate
                options.setOption(DigilibOption.hmir);
                rotation = rotation.substring(1);
            }
            try {
                float rot = Float.parseFloat(rotation);
                setValue("rot", rot);
            } catch (NumberFormatException e) {
                errorMessage = "Error parsing rotation parameter in IIIF path! ";
                logger.error(errorMessage+e);
                return false;
            }
        }
        
        /*
         * parameter quality
         */
        if (quality != null) {
            // quality param
            if (quality.equals("default") || quality.equals("native") || quality.equals("color")) {
                // color is default anyway
            } else if (quality.equals("gray") || quality.equals("grey")) {
                setValueFromString("colop", "grayscale");
            } else if (quality.equals("bitonal")) {
                setValueFromString("colop", "bitonal");
            } else {
                errorMessage = "Invalid quality parameter in IIIF path!";
                logger.error(errorMessage);
                return false;
            }
        }
        
        /*
         * parameter format
         */
        if (format != null) {
            // format param (we only support jpg and png)
            if (format.equals("jpg")) {
                // force jpg
                options.setOption(DigilibOption.jpg);
            } else if (format.equals("png")) {
                // force png
                options.setOption(DigilibOption.png);
            } else {
                errorMessage = "Invalid format parameter in IIIF path!";
                logger.error(errorMessage);
                return false;
            }
        }
        return true;
	}

	/**
	 * @param identifier
	 * @return
	 * @throws UnsupportedEncodingException
	 */
	public String decodeIiifIdentifier(String identifier) throws UnsupportedEncodingException {
		if (identifier.contains("%")) {
		    // still escape chars -- decode again
		    identifier = URLDecoder.decode(identifier, "UTF-8");
		}
		if (iiifSlashReplacement != null && identifier.contains(iiifSlashReplacement)) {
		    // change replacement back to slash
		    identifier = identifier.replace(iiifSlashReplacement, "/");
		}
		return identifier;
	}

	
    /**
     * Test if option string <code>opt</code> is set. Checks if the substring
     * <code>opt</code> is contained in the options string <code>param</code>.
     * 
     * @param opt
     *            Option string to be tested.
     * @return boolean
     * 
     * @deprecated use {@llink #hasOption(String opt)} for "mo"-options.
     */
    public boolean hasOption(String param, String opt) {
        String s = getAsString(param);
        if (s != null) {
            StringTokenizer i = new StringTokenizer(s, ",");
            while (i.hasMoreTokens()) {
                if (i.nextToken().equals(opt)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * The image file path to be accessed.
     * 
     * The image file path is assembled from the servlets RequestPath and
     * Parameter fn and normalized.
     * 
     * @return String the effective filepath.
     */
    public String getFilePath() {
        String s = getAsString("request.path");
        s += getAsString("fn");
        return FileOps.normalName(s);
    }

    /**
     * @return the ticket
     */
    public ImageJobDescription getJobDescription() {
        return ticket;
    }

    /**
     * @param ticket
     *            the ticket to set
     */
    public void setJobDescription(ImageJobDescription ticket) {
        this.ticket = ticket;
    }

    /**
     * @return the config
     */
    public DigilibConfiguration getDigilibConfig() {
        return config;
    }

    /**
     * @param config
     *            the config to set
     */
    public void setDigilibConfig(DigilibConfiguration config) {
        this.config = config;
    }

}