Package org.openfaces.util

Source Code of org.openfaces.util.AjaxUtil

/*
* OpenFaces - JSF Component Library 2.0
* Copyright (C) 2007-2012, TeamDev Ltd.
* licensing@openfaces.org
* Unless agreed in writing the contents of this file are subject to
* the GNU Lesser General Public License Version 2.1 (the "LGPL" License).
* This library 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.
* Please visit http://openfaces.org/licensing/ for more details.
*/
package org.openfaces.util;

import org.openfaces.org.json.JSONException;
import org.openfaces.org.json.JSONObject;

import javax.faces.component.NamingContainer;
import javax.faces.component.UIComponent;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.portlet.PortletRequest;
import javax.portlet.RenderRequest;
import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

/**
* @author Eugene Goncharov
*/
public class AjaxUtil {
    public static final String AJAX_REQUEST_MARKER = "_openFaces_ajax";
    public static final String PARAM_RENDER = "_of_render";
    private static final String COMPONENT_DELIMITER = ",";
    public static final String UPDATE_PORTIONS_SUFFIX = "_of_ajax_portions";
    public static final String CUSTOM_JSON_PARAM = "_of_customJsonParam";

    public static final String KEY_RENDERING_PORTLETS_AJAX_RESPONSE = AjaxUtil.class.getName() + ".renderingPortletsAjaxResponse";
    private static final String STATE_CHARSET = "ISO-8859-1";
    private static final int LENGTH_UNICODE = 4;
    private static final int LENGTH_BACKSLASH_AND_UNICODE = 5;

    private static long iInitLibraryNameCounter = 0;
    private static final Random random = new Random();
    public static final String ATTR_PORTLET_UNIQUE_RTLIBRARY_NAME = "org.openfaces.portletUniqueRTLibraryName";
    public static final String AJAX_SUPPORT_RENDERED = "org.openfaces.ajaxSupportOnPageRendered";

    /**
     * Runtime javascript library unique name generator. Runtime library is required for third-party JSF components
     * support in OpenFaces Ajaxed components in case when third-party components uses own JavaScript for initialization
     * and these scripts contains global variables declarations. In such situation simple javascript eval() function does
     * not work correctly under IE (meaning that global variables declared in script executed by eval() under IE would
     * not be declared).
     *
     * @return string with unique name for runtime library
     */
    public static String generateUniqueInitLibraryName() {
        long partOne = System.currentTimeMillis();
        int partTwo = random.nextInt(1000);
        long partThree = iInitLibraryNameCounter++;
        if (iInitLibraryNameCounter == Long.MAX_VALUE) iInitLibraryNameCounter = 0;

        StringBuilder buffer = new StringBuilder().append(partOne).append(partTwo).append(partThree).append(".js");
        return buffer.toString();
    }

    public static boolean isAjax4jsfRequest() {
        ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
        Class richFacesContextClazz = null;
        Boolean result;
        try {
            Object currentInstance = null;

            try {
                richFacesContextClazz = Class.forName("org.ajax4jsf.context.AjaxContext");
            } catch (ClassNotFoundException e) {
                // absence of RichFaces is a valid case
            }

            if (richFacesContextClazz != null) {
                Method currentInstanceMethod = richFacesContextClazz.getMethod("getCurrentInstance", FacesContext.class);
                if (currentInstanceMethod != null) {
                    currentInstance = currentInstanceMethod.invoke(null, FacesContext.getCurrentInstance());
                }

                if (currentInstance != null) {
                    boolean isFacesContextInUse = true;
                    Method isAjaxRequestMethod = null;

                    try {
                        isAjaxRequestMethod = richFacesContextClazz.getMethod("isAjaxRequest", FacesContext.class);
                    } catch (NoSuchMethodException e) {
                        // We will try to search for this method without FacesContext in params
                    } catch (SecurityException e) {
                        //
                    }

                    if (isAjaxRequestMethod == null) {
                        isAjaxRequestMethod = richFacesContextClazz.getMethod("isAjaxRequest");
                        isFacesContextInUse = false;
                    }

                    if (isAjaxRequestMethod != null) {
                        result = (isFacesContextInUse)
                                ? (Boolean) isAjaxRequestMethod.invoke(currentInstance, FacesContext.getCurrentInstance())
                                : (Boolean) isAjaxRequestMethod.invoke(currentInstance);
                        if (result != null)
                            return result;
                    }
                }
            }
        } catch (NoSuchMethodException e) {
            externalContext.log("Ajax4jsf support is disabled, because getCurrentInstance method was not " +
                    "found in org.ajax4jsf.context.AjaxContext.");
            return false;
        } catch (IllegalAccessException e) {
            externalContext.log("Ajax4jsf support is disabled because exception was thrown during " +
                    "execution of getCurrentInstance method in org.ajax4jsf.context.AjaxContext");
            return false;
        } catch (InvocationTargetException e) {
            externalContext.log("Ajax4jsf support is disabled because exception was thrown during " +
                    "execution of getCurrentInstance method in org.ajax4jsf.context.AjaxContext");
            return false;
        }
        return false;
    }

    /**
     * Checks whether the current request is an Ajax request
     *
     * @param request incoming request.
     * @return <code>true</code> - if the current request is an Ajax request. <code>false</code> otherwise.
     */
    public static boolean isAjaxRequest(RequestFacade request) {
        // for portlets: String browser = request.getProperty(...);
        return request.getParameter(AJAX_REQUEST_MARKER) != null;
    }

    public static boolean isAjaxRequest(FacesContext context) {
        if (isPortletRenderRequest(context)) {
            Map<String, Object> sessionMap = context.getExternalContext().getSessionMap();
            boolean ajaxKeyExists = sessionMap.containsKey(AJAX_REQUEST_MARKER);
            if (ajaxKeyExists)
                return true;
            // isPortletRenderRequest also returns true for action requests under Liferay, so it's safe to perform the default check to address this
        }

        ExternalContext externalContext = context.getExternalContext();
        if (isPortletRequest(FacesContext.getCurrentInstance())) {
            RequestFacade request = RequestFacade.getInstance(externalContext.getRequest());
            // for portlets: String browser = request.getProperty(...);
            return request.getParameter(AJAX_REQUEST_MARKER) != null;
        } else {
            HttpServletRequest request = (HttpServletRequest) externalContext.getRequest();
            return request.getParameter(AJAX_REQUEST_MARKER) != null;
        }
    }

    private static boolean considerPortlets = true;

    public static boolean isPortletRequest(FacesContext context) {
        if (!considerPortlets)
            return false;
        ExternalContext externalContext = context.getExternalContext();
        Object requestObj = externalContext.getRequest();
        try {
            return requestObj instanceof PortletRequest;
        } catch (NoClassDefFoundError e) {
            considerPortlets = false;
            return false;
        }
    }

    public static boolean isPortletRenderRequest(FacesContext context) {
        if (!considerPortlets)
            return false;
        ExternalContext externalContext = context.getExternalContext();
        Object requestObj = externalContext.getRequest();
        try {
            return requestObj instanceof RenderRequest;
        } catch (NoClassDefFoundError e) {
            considerPortlets = false;
            return false;
        }
    }

    public static String getComponentStateFieldName(String clientId) {
        return clientId + "::_state";
    }

    public static String objectToString(Object object) throws IOException {
        byte[] bytes;
        {
            ByteArrayOutputStream compressedBytesStream = new ByteArrayOutputStream();
            OutputStream compressingStream = new GZIPOutputStream(compressedBytesStream);
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(compressingStream);
            try {
                objectOutputStream.writeObject(object);
            } finally {
                objectOutputStream.close();
                compressingStream.close();
                compressedBytesStream.close();
            }
            bytes = compressedBytesStream.toByteArray();
        }
        Base64 base64 = new Base64();
        byte[] encodedBytes = base64.encode(bytes);
        return new String(encodedBytes, STATE_CHARSET);
    }

    public static void prepareComponentForAjax(FacesContext context, UIComponent component) {
        if (!component.isRendered())
            return;
        if (isAjaxRequest(context))
            return;
        renderJSLinks(context);
        makeComponentsNonTransient(context, component);
    }

    public static void renderJSLinks(FacesContext context) {
        if (isAjaxRequest(context))
            return;
        ExternalContext externalContext = context.getExternalContext();
        Map<String, Object> requestMap = externalContext.getRequestMap();
        if (requestMap.put(AJAX_SUPPORT_RENDERED, Boolean.TRUE) == null) {
            try {
                renderAjaxSupport(context);
                if (isPortletRequest(context)) {
                    String uniqueRTLibraryName = ResourceFilter.RUNTIME_INIT_LIBRARY_PATH + generateUniqueInitLibraryName();
                    context.getExternalContext().getSessionMap().put(ATTR_PORTLET_UNIQUE_RTLIBRARY_NAME, uniqueRTLibraryName);
                    String initLibraryUrl = Resources.applicationURL(context, uniqueRTLibraryName);
                    Resources.renderJSLinkIfNeeded(context, initLibraryUrl);
                }

            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public static void renderAjaxSupport(FacesContext context) throws IOException {
        Resources.renderJSLinkIfNeeded(context, Resources.utilJsURL(context));
        Resources.renderJSLinkIfNeeded(context, Resources.ajaxUtilJsURL(context));
        UtilPhaseListener.encodeFormSubmissionAjaxInactivityTimeout(context);
    }

    public static Object retrieveAjaxStateObject(FacesContext context, UIComponent component) {
        Object stateObj;
        try {
            String componentId = getComponentId(component);
            stateObj = readState(context, componentId);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return stateObj;
    }

    private static String getComponentId(UIComponent component) {
        UIComponent namingContainer = component.getParent();
        for (; namingContainer != null;
             namingContainer = namingContainer.getParent())
            if (namingContainer instanceof NamingContainer)
                break;
        StringBuilder buf = new StringBuilder();
        if (namingContainer != null)
            buf.append(getComponentId(namingContainer)).append(NamingContainer.SEPARATOR_CHAR);
        buf.append(component.getId());
        return buf.toString();
    }

    private static Object readState(FacesContext context, String clientId) throws IOException {
        Map<String, String> requestParameterMap = context.getExternalContext().getRequestParameterMap();
        String stateFieldName = getComponentStateFieldName(clientId);
        String stateStr = requestParameterMap.get(stateFieldName);
        if (stateStr == null)
            return null;
        return stringToObject(stateStr);
    }

    private static Object stringToObject(String str) throws IOException {
        Base64 base64 = new Base64();
        byte[] encodedBytes = str.getBytes(STATE_CHARSET);
        byte[] decodedBytes = base64.decode(encodedBytes);
        ByteArrayInputStream compressedBytesStream = new ByteArrayInputStream(decodedBytes);
        InputStream decompressingStream = new GZIPInputStream(compressedBytesStream);
        ObjectInputStream objectInputStream = new ObjectInputStream(decompressingStream);
        try {
            return objectInputStream.readObject();
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } finally {
            objectInputStream.close();
            decompressingStream.close();
            compressedBytesStream.close();
        }
    }

    /**
     * This method should be called for a component that is using AJAX before it is rendered. This method makes all
     * sub-components non-transient to avoid losing component state on AJAX requests.
     */
    private static void makeComponentsNonTransient(FacesContext context, UIComponent component) {
        if (component.isTransient()) {
            component.setTransient(false);
            if (component.isTransient()) {
                if (!component.getClass().getName().equalsIgnoreCase("com.sun.facelets.compiler.UIInstructions")) {
                    Rendering.logWarning(context, "Couldn't reset 'transient' flag of component to false for Ajax request to maintain component state correctly. " +
                            "Component's clientId = " + component.getClientId(context) + "; component class: " + component.getClass());
                }
            }
        }
        Iterator<UIComponent> descendants = component.getFacetsAndChildren();
        while (descendants.hasNext()) {
            UIComponent descendant = descendants.next();
            makeComponentsNonTransient(context, descendant);
        }
    }

    public static boolean getSkipExtraRenderingOnPortletsAjax(FacesContext context) {
        if (!isAjaxRequest(context) || !isPortletRequest(context))
            return false;
        boolean skipRendering = !isRenderingPortletsAjaxResponse(context);
        return skipRendering;
    }

    public static boolean isRenderingPortletsAjaxResponse(FacesContext context) {
        Map<String, Object> requestMap = context.getExternalContext().getRequestMap();
        boolean result = requestMap.containsKey(KEY_RENDERING_PORTLETS_AJAX_RESPONSE);
        return result;
    }

    public static List<String> getRequestedAjaxPortionNames(FacesContext context) {
        ExternalContext externalContext = context.getExternalContext();
        RequestFacade request = RequestFacade.getInstance(externalContext.getRequest());
        return getAjaxPortionNames(context, request);
    }

    /**
     * This method should be used by components to ensure the correct lifecycle of using Ajax under Portlets. The specifics
     * of Ajax under Portlets is that as opposed to other configurations, component's encoding functions are invoked even
     * if the ajax request is sent to render the component's portions only. The components which can potentially have a
     * problem with such double rendering should invoke this method and skip the rendering functions if the current is
     * an Ajax portion request.
     *
     * @return true if the current request is an ajax request for retrieving portions from the specified component. This method
     *         performs its primary job only during the render phase under portlets. In other conditions it always returns false.
     */
    public static boolean isAjaxPortionRequest(FacesContext context, UIComponent component) {
        if (!isAjaxRequest(context))
            return false;
        RequestFacade request = RequestFacade.getInstance(context.getExternalContext().getRequest());
        List<String> portionNames = getAjaxPortionNames(context, request);
        if (portionNames == null || portionNames.size() == 0)
            return false;
        Map sessionMap = context.getExternalContext().getSessionMap();
        String componentId = (String) sessionMap.get(PARAM_RENDER);
        if (componentId == null) // not portlets
            componentId = request.getParameter(PARAM_RENDER);
        boolean requestForSpecifiedComponent = component.getClientId(context).equals(componentId);
        return requestForSpecifiedComponent;

    }

    public static JSONObject getCustomJSONParam(FacesContext context, RequestFacade request) {
        String jsonParamStr = isPortletRequest(context)
                ? (String) context.getExternalContext().getSessionMap().get(CUSTOM_JSON_PARAM)
                : request.getParameter(CUSTOM_JSON_PARAM);
        if (jsonParamStr == null)
            return null;
        JSONObject param;
        try {
            param = new JSONObject(jsonParamStr);
        } catch (JSONException e) {
            throw new RuntimeException(e);
        }
        return param;
    }

    public static List<String> getAjaxPortionNames(FacesContext context, RequestFacade request) {
        String updateIds = isPortletRequest(context)
                ? (String) context.getExternalContext().getSessionMap().get(UPDATE_PORTIONS_SUFFIX)
                : request.getParameter(UPDATE_PORTIONS_SUFFIX);
        List<String> result = new ArrayList<String>();
        if (updateIds == null || updateIds.equals("")) {
            return Collections.emptyList();
        }
        int idx = updateIds.indexOf(COMPONENT_DELIMITER);
        if (idx == -1) {
            result.add(unescapeSymbol(updateIds.trim()));
        } else {
            while (idx != -1) {
                String part = updateIds.substring(0, idx);
                result.add(unescapeSymbol(part).trim());
                updateIds = updateIds.substring(idx + 1);
                idx = updateIds.indexOf(COMPONENT_DELIMITER);
            }
            result.add(updateIds.trim());
        }
        return result;
    }

    public static String unescapeSymbol(String str) {
        StringBuilder buf = new StringBuilder();
        char[] chars = str.toCharArray();
        for (int i = 0; i < str.length(); ) {
            char c = chars[i];
            if (c == '\\' && i < str.length() - LENGTH_UNICODE) {
                if (chars[i + 1] == '\\') {
                    buf.append('\\');
                    i = i + 2;
                    continue;
                } else {
                    buf.append((char) Integer.parseInt(str.substring(i + 1, i + LENGTH_BACKSLASH_AND_UNICODE)));
                    i = i + LENGTH_BACKSLASH_AND_UNICODE;
                    continue;
                }
            }
            buf.append(c);
            i++;
        }
        return buf.toString();
    }
}
TOP

Related Classes of org.openfaces.util.AjaxUtil

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.