Subversion Repositories general

Rev

Rev 956 | Rev 993 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

package ak.backpath;

import java.io.InputStream;
import java.io.OutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Iterator;
import java.util.Map;
import java.util.ArrayList;
import java.util.Set;
import java.util.TreeSet;
import java.util.Collections;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterInputStream;
import java.net.URL;
import java.net.URLEncoder;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class BackPath
{
        private static Log log = LogFactory.getLog(BackPath.class);

        public static final String   DEFAULT_KEY            = BackPath.class.getName();
        public static final String   DEFAULT_PARAM          = "backpath";
        public static final String   OUT_CHARSET            = "UTF-8";
        public static final String   URL_CHARSET            = "UTF-8";
        public static final String   IGNORES_DELIMITER      = "\\s*;\\s*";
        public static final boolean  DEFAULT_ZIP            = true;
        public static final String[] METHODS_TO_SAVE_PARAMS = {"GET"};
        public static final char     URL_PARAM_START        = '?';
        public static final char     URL_PARAM_SEPARATOR    = '&';
        public static final char     URL_PARAM_VALUE        = '=';
        public static final char     URL_PARAM_VALUE_SEP    = ',';

        protected static final byte   URL_DELIMITER         = 0;
        protected static final int    BUFFER_SIZE           = 1024;
        protected static final int    INIT_STREAM_SIZE_COEF = 100;
        protected static final String URL_PARAM_START_STR   = "" + URL_PARAM_START;

        protected static Set methodsToSaveParams;

        // List(String)
        protected List     stack = new ArrayList();
        protected String   param = DEFAULT_PARAM;
    protected String[] ignoreParams = null;

        /** zip the stack */
        protected boolean zip = DEFAULT_ZIP;

        // string cache
        protected String  forwardParams;
        protected boolean forwardParamsFormed  = false;
        protected String  currentParams;
        protected boolean currentParamsFormed  = false;
        protected String  backwardParams;
        protected boolean backwardParamsFormed = false;
        protected String  backwardUrl;
        protected boolean backwardUrlFormed    = false;

    static {
                methodsToSaveParams = Collections.unmodifiableSet(
                        new TreeSet(Arrays.asList(METHODS_TO_SAVE_PARAMS)));
    }

        protected static Deflater deflater = new Deflater();
        protected static Inflater inflater = new Inflater();

        public static BackPath findBackPath(HttpServletRequest request)
                throws Exception
        {
                return findBackPath(request, DEFAULT_KEY, DEFAULT_PARAM, (String)null, DEFAULT_ZIP);
        }

        public static BackPath findBackPath(HttpServletRequest request, String backPathKey,
                        String backPathParam, String backPathIgnore, boolean zip)
                throws Exception
        {
                Object backPathObj = request.getAttribute(backPathKey);

                if(backPathObj == null) {
                        BackPath backPath;
                        String[] ignores = null;

            if(backPathIgnore != null)
                ignores = backPathIgnore.trim().split(IGNORES_DELIMITER);

                        backPath = new BackPath(request, backPathParam, ignores, zip);
                        request.setAttribute(backPathKey, backPath);
                        return backPath;
                }
                else if(backPathObj instanceof BackPath) {
                        return (BackPath)backPathObj;
                }
                else {
                        throw new Exception("An object under key " + backPathKey
                                + " is not a " + BackPath.class.getName());
                }
        }

        public BackPath(HttpServletRequest request, String param, String[] ignoreParams, boolean zip)
        {
                this.zip          = zip;
                this.param        = param;
                this.ignoreParams = ignoreParams;
                init(request);
        }

        public boolean getZip()
        {
                return zip;
        }

        public void setZip(boolean zip)
        {
                this.zip = zip;
                resetCachedStrings();
        }

        public String getParam()
        {
                return param;
        }

        public void setParam(String param)
        {
                this.param = param;
                resetCachedStrings();
        }

        public String[] getIgnoreParams()
        {
                return ignoreParams;
        }

        public void setIgnoreParams(String[] ignoreParams)
        {
                this.ignoreParams = ignoreParams;
                resetCachedStrings();
        }

        public boolean getHasBack()
        {
                return (stack.size() > 1);
        }

        // List(String)
        public List getStack()
        {
                return Collections.unmodifiableList(stack);
        }

        public String getForwardParams()
        {
                if(!forwardParamsFormed) {
                        forwardParams       = formForwardParams();
                        forwardParamsFormed = true;
                }

                return forwardParams;
        }

        protected String formForwardParams()
        {
                try {
                        return encode(stack, zip, 0);
                }
                catch(IOException ex) {
                        log.error("Cannot form forward params", ex);
                        return null;
                }
        }

        public String getBackwardParams()
        {
                if(!backwardParamsFormed) {
                        backwardParams       = formBackwardParams();
                        backwardParamsFormed = true;
                }

                return backwardParams;
        }

        protected String formBackwardParams()
        {
                try {
                        if(stack.size() <= 2)
                                return null;
                        else
                                return encode(stack, zip, 2);
                }
                catch(IOException ex) {
                        log.error("Cannot form backward params", ex);
                        return null;
                }
        }

        public String getBackwardUrl()
        {
                if(!backwardParamsFormed) {
                        backwardUrl       = formBackwardUrl();
                        backwardUrlFormed = true;
                }

                return backwardUrl;
        }

        protected String formBackwardUrl()
        {
                if(stack.size() < 2) return null;

                try {
                        StringBuffer url = new StringBuffer((String)stack.get(stack.size()-2));
                        if(stack.size() > 2) {
                                String s = encode(stack, zip, 2);
                                if(url.indexOf(URL_PARAM_START_STR) >= 0) url.append(URL_PARAM_SEPARATOR);
                                else                                      url.append(URL_PARAM_START);
                                url.append(param).append(URL_PARAM_VALUE).append(s);
                        }

                        return url.toString();
                }
                catch(IOException ex) {
                        log.error("Cannot form backward url", ex);
                        return null;
                }
        }

        public String getCurrentParams()
        {
                if(!currentParamsFormed) {
                        currentParams       = formCurrentParams();
                        currentParamsFormed = true;
                }

                return currentParams;
        }

        protected String formCurrentParams()
        {
                try {
                        if(stack.size() <= 1)
                                return null;
                        else
                                return encode(stack, zip, 1);
                }
                catch(IOException ex) {
                        log.error("Cannot form current params", ex);
                        return null;
                }
        }

        public void init(HttpServletRequest request)
        {
                try {
                        resetCachedStrings();
                        // get old
                        decode(stack, request.getParameter(param), zip);

                        // add new
                        String url = (new URL(request.getRequestURL().toString())).getPath();
                        if(methodsToSaveParams.contains(request.getMethod())) {
                                // to ignore
                                Set ignore = new TreeSet();
                                if(ignoreParams != null) {
                                        for(int i = 0; i < ignoreParams.length; i++)
                                                ignore.add(ignoreParams[i]);
                                }
                                ignore.add(param);

                                // form query string
                                StringBuffer query = new StringBuffer();
                                Map requestParams = request.getParameterMap();
                                for(Iterator i = requestParams.keySet().iterator(); i.hasNext(); ) {
                                        String name = (String)i.next();
                                        if(ignore.contains(name)) continue;
                                        if(query.length() == 0)
                                                query.append(URL_PARAM_START);
                                        else
                                                query.append(URL_PARAM_SEPARATOR);

                                        query.append(URLEncoder.encode(name, URL_CHARSET));
                                        query.append(URL_PARAM_VALUE);
                                        String[] values = (String[])requestParams.get(name);
                                        for(int j = 0; j < values.length; j++) {
                                                if(j > 0) query.append(URL_PARAM_VALUE_SEP);
                                                query.append(URLEncoder.encode(values[j], URL_CHARSET));
                                        }
                                }

                                // form url
                                if(query.length() > 0) url = query.insert(0, url).toString();
                        }
                        stack.add(url);
                }
                catch(IOException ex) {
                        log.error("Cannot init", ex);
                }
        }

        /**
         * @param stack List(String)
         */
        protected String encode(List stack, boolean zip, int suffix)
                throws IOException
        {
                ByteArrayOutputStream buf = new ByteArrayOutputStream(stack.size()*INIT_STREAM_SIZE_COEF);
                Object lock = (zip ? (Object)BackPath.class : this);

                synchronized(lock) {
                        OutputStream out;
                        if(zip) {
                                deflater.reset();
                                out = new DeflaterOutputStream(new Base64.OutputStream(buf), deflater);
                        }
                        else {
                                out = new Base64.OutputStream(buf);
                        }

                        for(int i = 0; i < stack.size()-suffix; i++) {
                                String s = (String)stack.get(i);
                                if(i > 0) out.write(URL_DELIMITER);
                                out.write(s.getBytes(URL_CHARSET));
                        }
                        out.close();
                }

                return buf.toString(OUT_CHARSET);
        }

        /**
         * @param stack List(String)
         */
        protected void decode(List stack, String path, boolean zip)
                throws IOException
        {
                stack.clear();
                if(path == null || path.equals("")) return;

                ByteArrayOutputStream dec = new ByteArrayOutputStream(stack.size()*INIT_STREAM_SIZE_COEF);
                Object lock = (zip ? (Object)BackPath.class : this);

                synchronized(lock) {
                        ByteArrayInputStream enc = new ByteArrayInputStream(path.getBytes(OUT_CHARSET));

                        InputStream in;
                        if(zip) {
                                inflater.reset();
                                in = new InflaterInputStream(new Base64.InputStream(enc), inflater);
                        }
                        else {
                                in = new Base64.InputStream(enc);
                        }

                        byte[] tmp = new byte[BUFFER_SIZE];
                        int    l;
                        while((l = in.read(tmp, 0, tmp.length)) > 0) {
                                dec.write(tmp, 0, l);
                        }
                }

                try {
                        byte[] buf = dec.toByteArray();
                        for(int start = 0, end = 0; end <= buf.length; end++) {
                                if(end == buf.length || buf[end] == URL_DELIMITER) {
                                        stack.add(new String(buf, start, end-start, URL_CHARSET));
                                        start = end+1;
                                }
                        }
                }
                catch(Exception ex) {  // if some mistake in stack, then ignore it completely
                        stack.clear();
                        log.info("Cannot parse stack", ex);
                }
        }

        protected void resetCachedStrings()
        {
                forwardParams        = null;
                forwardParamsFormed  = false;
                currentParams        = null;
                currentParamsFormed  = false;
                backwardParams       = null;
                backwardParamsFormed = false;
                backwardUrl          = null;
                backwardUrlFormed    = false;
        }
}