com.atolcd.alfresco.AuditFilter.java Source code

Java tutorial

Introduction

Here is the source code for com.atolcd.alfresco.AuditFilter.java

Source

/*
 * Copyright (C) 2013 Atol Conseils et Dveloppements.
 * http://www.atolcd.com/
 *
 * 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package com.atolcd.alfresco;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.httpclient.URIException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.context.ApplicationContext;
import org.springframework.extensions.surf.FrameworkUtil;
import org.springframework.extensions.surf.RequestContext;
import org.springframework.extensions.surf.RequestContextUtil;
import org.springframework.extensions.surf.exception.ConnectorServiceException;
import org.springframework.extensions.surf.exception.RequestContextException;
import org.springframework.extensions.surf.exception.ResourceLoaderException;
import org.springframework.extensions.surf.exception.UserFactoryException;
import org.springframework.extensions.surf.support.AlfrescoUserFactory;
import org.springframework.extensions.surf.support.ThreadLocalRequestContext;
import org.springframework.extensions.surf.util.I18NUtil;
import org.springframework.extensions.webscripts.Status;
import org.springframework.extensions.webscripts.connector.Connector;
import org.springframework.extensions.webscripts.connector.ConnectorContext;
import org.springframework.extensions.webscripts.connector.HttpMethod;
import org.springframework.extensions.webscripts.connector.Response;
import org.springframework.extensions.webscripts.connector.User;
import org.springframework.web.context.support.WebApplicationContextUtils;

@SuppressWarnings("deprecation")
public class AuditFilter extends AuditFilterConstants implements Filter {
    // Logger
    private static final Log logger = LogFactory.getLog(AuditFilter.class);

    public static final String KEY_SITE = "site";
    public static final String KEY_MODULE = "module";
    public static final String KEY_OBJECT = "object";
    public static final String KEY_ACTION = "action";

    private ServletContext servletContext;
    // Store the "object" parameters to get for each module
    private static HashMap<String, String> moduleIds;
    private static HashMap<String, String[]> ignoredCases;
    private static Set<String> ignoredUrl;

    // Id used to identify an audit (in the database) from the repository (and
    // not from a site).
    private static final String REPOSITORY_SITE = "/service";

    static {
        // Exact URLs to be filtered
        ignoredUrl = new HashSet<String>();
        ignoredUrl.add("/share/page");
        ignoredUrl.add("/share/page/");
        ignoredUrl.add("/share/page/dologin");
        ignoredUrl.add("/share/page/dologin/");
        ignoredUrl.add("/share/page/dologout");
        ignoredUrl.add("/share/page/dologout/");
        ignoredUrl.add("/share/page/console");
        ignoredUrl.add("/share/page/console/");

        // Ignored cases (sites)
        ignoredCases = new HashMap<String, String[]>();
        ignoredCases.put("wiki", new String[] { "create" });
        ignoredCases.put("blog", new String[] { "postedit" });
        ignoredCases.put("links", new String[] { "linkedit" });
        ignoredCases.put("discussions", new String[] { "createtopic" });
        ignoredCases.put("calendar", new String[] { "month", "week", "day" });

        // Array: 'module -> parameters'
        // Used to read 'audit' settings
        moduleIds = new HashMap<String, String>();
        moduleIds.put("wiki", "title");
        moduleIds.put("blog", "postId");
        moduleIds.put("document", "nodeRef");
        moduleIds.put("documentlibrary", "filter");
        moduleIds.put("calendar", "view");
        moduleIds.put("links", "linkId");
        moduleIds.put("discussions", "topicId");
        moduleIds.put("data", "list");
        moduleIds.put("members", "");
        moduleIds.put("dashboard", "");
        moduleIds.put("search", "t");
    }

    @Override
    public void destroy() {
    }

    @Override
    public void init(FilterConfig args) throws ServletException {
        this.servletContext = args.getServletContext();
    }

    private ApplicationContext getApplicationContext() {
        return WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
    }

    @Override
    public void doFilter(ServletRequest sReq, ServletResponse sRes, FilterChain chain)
            throws IOException, ServletException {

        // Get the HTTP request/response/session
        HttpServletRequest request = (HttpServletRequest) sReq;
        // HttpServletResponse response = (HttpServletResponse) sRes;

        // Initialize a new request context
        RequestContext context = ThreadLocalRequestContext.getRequestContext();

        if (context == null) {
            try {
                // Perform a "silent" init - i.e. no user creation or remote
                // connections
                context = RequestContextUtil.initRequestContext(getApplicationContext(), request, true);
                try {
                    RequestContextUtil.populateRequestContext(context, request);
                } catch (ResourceLoaderException e) {
                    // e.printStackTrace();
                } catch (UserFactoryException e) {
                    // e.printStackTrace();
                }
            } catch (RequestContextException ex) {
                throw new ServletException(ex);
            }
        }
        User user = context.getUser();
        String requestURI = request.getRequestURI();
        if (user != null && requestURI != null && !ignoredUrl.contains(requestURI)) {
            try {
                // Preparation of JSON to send.
                JSONObject auditSample = new JSONObject();
                auditSample.put(AUDIT_ID, "0");
                auditSample.put(AUDIT_USER_ID, user.getId());

                String ref = request.getHeader("referer");
                if (requestURI.endsWith("/dologin") && (ref != null)) {
                    requestURI = ref;
                }
                HashMap<String, String> auditData = getAuditData(request, user.getId(), requestURI);
                // Parsing sometimes includes parameters when the page loading
                // is interrupted prematurely
                if ((auditData.get(KEY_MODULE).length() > 0) && (!auditData.get(KEY_ACTION).contains("?"))) {
                    auditSample.put(AUDIT_SITE, auditData.get(KEY_SITE));
                    auditSample.put(AUDIT_APP_NAME, auditData.get(KEY_MODULE));
                    auditSample.put(AUDIT_ACTION_NAME, auditData.get(KEY_ACTION));
                    auditSample.put(AUDIT_OBJECT, auditData.get(KEY_OBJECT));
                    auditSample.put(AUDIT_TIME, Long.toString(System.currentTimeMillis()));

                    // Remote call for DB
                    if (moduleIds.containsKey(auditSample.get(AUDIT_APP_NAME))) {
                        remoteCall(request, auditSample);
                    } else {
                        logger.info("Ignored: " + requestURI);
                    }
                }
            } catch (Exception e) {
                logger.error(" Error while auditing data in AuditFilter");
                if (logger.isDebugEnabled()) {
                    logger.debug(e.getMessage(), e);
                }
            }
        }
        chain.doFilter(sReq, sRes);
    }

    private void remoteCall(HttpServletRequest request, JSONObject auditSample)
            throws JSONException, URIException, UnsupportedEncodingException {
        Connector connector;
        try {
            connector = FrameworkUtil.getConnector(request.getSession(true), auditSample.getString(AUDIT_USER_ID),
                    AlfrescoUserFactory.ALFRESCO_ENDPOINT_ID);
            // if (parameters == null), we use the 'inputstream'
            // The webscript is called with the audit converted into JSON.
            ConnectorContext postContext = new ConnectorContext(null, buildDefaultHeaders());
            postContext.setMethod(HttpMethod.POST);
            postContext.setContentType("text/plain;charset=UTF-8");
            InputStream in = new ByteArrayInputStream(auditSample.toString().getBytes("UTF-8"));

            // Webscript call
            connector.call("/share-stats/insert-audit", postContext, in);
        } catch (ConnectorServiceException e) {
            if (logger.isDebugEnabled()) {
                logger.debug(e.getMessage(), e);
            }
        }
    }

    /**
     * @param request
     * @param userId
     * @param siteId
     * @param componentId
     * @param objectId
     * @return
     * @throws JSONException
     * @throws URIException
     * @throws UnsupportedEncodingException
     */
    private String getNodeRefRemoteCall(HttpServletRequest request, String userId, String siteId,
            String componentId, String objectId) throws JSONException, URIException, UnsupportedEncodingException {
        Connector connector;
        try {
            connector = FrameworkUtil.getConnector(request.getSession(true), userId,
                    AlfrescoUserFactory.ALFRESCO_ENDPOINT_ID);
            // <url>/share-stats/slingshot/details/{siteId}/{componentId}/{objectId}</url>
            Response resp = connector.call("/share-stats/slingshot/details/" + siteId + "/" + componentId + "/"
                    + URLEncoder.encode(objectId, "UTF-8"));

            if (resp.getStatus().getCode() == Status.STATUS_OK) {
                try {
                    JSONObject json = new JSONObject(resp.getResponse());
                    if (json.has("nodeRef")) {
                        return (String) json.get("nodeRef");
                    }
                } catch (JSONException e) {
                    if (logger.isDebugEnabled()) {
                        logger.debug(e.getMessage(), e);
                    }
                }
            }
        } catch (ConnectorServiceException e) {
            if (logger.isDebugEnabled()) {
                logger.debug(e.getMessage(), e);
            }
        }

        return objectId;
    }

    /**
     * Cut and analysis the url and analysis parameters
     * 
     * @param request
     *            HttpServletRequest
     * @param requestURL
     *            String
     * @return HashMap
     */
    public HashMap<String, String> getAuditData(HttpServletRequest request, String userId, String requestURL) {
        HashMap<String, String> auditData = new HashMap<String, String>();
        String[] urlTokens = requestURL.split("/");

        HashMap<String, String> urlData = getUrlData(urlTokens, requestURL.indexOf("site") != -1);

        auditData.putAll(urlData);

        try {
            // Retrieves the id of the <<objet>> viewed from its module
            String obj = request.getParameter(moduleIds.get(urlData.get(KEY_MODULE)));
            if (obj != null) {
                if (auditData.get(KEY_MODULE).equals("calendar")) {
                    auditData.put(KEY_ACTION, obj);
                    auditData.put(KEY_OBJECT, "");
                } else if (auditData.get(KEY_MODULE).equals(MOD_LINKS)
                        && auditData.get(KEY_ACTION).equals("view")) {
                    String auditObject = getNodeRefRemoteCall(request, userId, auditData.get(KEY_SITE),
                            auditData.get(KEY_MODULE), obj);
                    auditData.put(KEY_OBJECT, auditObject);
                    auditData.put(KEY_ACTION, "single-view");
                } else if (auditData.get("module").equals("search")) {
                    if (!obj.isEmpty()) {
                        auditData.put(KEY_ACTION, "query");
                        auditData.put(KEY_OBJECT, obj);
                    } else {
                        auditData.put(KEY_OBJECT, "");
                    }

                } else {
                    String auditObject = getNodeRefRemoteCall(request, userId, auditData.get(KEY_SITE),
                            auditData.get(KEY_MODULE), obj);
                    auditData.put(KEY_OBJECT, auditObject);
                }
            } else {
                auditData.put(KEY_OBJECT, "");
            }
        } catch (Exception e) {
            auditData.put(KEY_OBJECT, "");
        }

        return filter(auditData);
    }

    /**
     * URL parsing
     * 
     * @param urlTokens
     * @param hasSite
     * @return HashMap
     */
    public HashMap<String, String> getUrlData(String[] urlTokens, boolean hasSite) {
        HashMap<String, String> urlData = new HashMap<String, String>();
        urlData.put(KEY_MODULE, "");
        urlData.put(KEY_ACTION, "");
        urlData.put(KEY_SITE, "");

        if (!hasSite) {
            String[] newUrlTokens = new String[urlTokens.length + 2];
            int j = 0;
            String token;
            for (int i = 0; i < urlTokens.length; i++) {
                token = urlTokens[i];
                newUrlTokens[i + j] = token;

                if (urlTokens[i].equals("page")) {
                    newUrlTokens[i + 1] = "site";
                    newUrlTokens[i + 2] = REPOSITORY_SITE;
                    j = 2;
                }
            }

            urlTokens = newUrlTokens;
        }

        boolean siteFlag = false;
        int limit = urlTokens.length - 1;
        for (int i = 0; i < urlTokens.length; i++) {
            if ("site".equals(urlTokens[i]) && !siteFlag) {
                siteFlag = true;
            } else if (siteFlag && (urlData.get(KEY_SITE).isEmpty()) && i < limit) {
                urlData.put(KEY_SITE, urlTokens[i]);
                String[] splittedModuleAction = urlTokens[i + 1].split("-");
                // "site-members" & "site-groups" test
                if (splittedModuleAction[0].equals("site")) {
                    urlData.put(KEY_MODULE, MOD_MEMBERS);
                } else {
                    urlData.put(KEY_MODULE, splittedModuleAction[0]);
                }

                // Action test (for example: module-action ; wiki-create)
                if (splittedModuleAction.length > 1) {
                    urlData.put(KEY_ACTION, splittedModuleAction[1]);
                } else if (splittedModuleAction.length == 1) {
                    if (urlData.get(KEY_MODULE).endsWith("library")) {
                        urlData.put(KEY_MODULE, MOD_DOCUMENT);
                        urlData.put(KEY_ACTION, "library");
                    } else if (urlData.get(KEY_MODULE).endsWith("search")) {
                        urlData.put(KEY_ACTION, splittedModuleAction[0]);
                        urlData.put(KEY_MODULE, "search");
                    } else {
                        urlData.put(KEY_ACTION, "view");
                    }
                }
            }
        }

        return urlData;
    }

    /**
     * Audit data filter
     * 
     * @param auditData
     * @return HashMap
     */
    public HashMap<String, String> filter(HashMap<String, String> auditData) {
        String module = auditData.get(KEY_MODULE);
        String action = auditData.get(KEY_ACTION);

        if (module != null && action != null) {
            if (ignoredCases.containsKey(module)) {
                if (contains(ignoredCases.get(module), action)) {
                    // No audited if it does not contain site
                    auditData.put(KEY_SITE, "");
                }
            }
        }
        return auditData;
    }

    /**
     * Checks if a String is contained into an array
     * 
     * @param array
     * @param toFind
     * @return boolean
     */
    public boolean contains(String[] array, String toFind) {
        boolean res = false;
        for (int i = 0; i < array.length; i++) {
            if (array[i].equals(toFind)) {
                res = true;
            }
        }
        return res;
    }

    /**
     * Helper to build a map of the default headers for script requests - we
     * send over the current users locale so it can be respected by any
     * appropriate REST APIs.
     * 
     * @return map of headers
     */
    private static Map<String, String> buildDefaultHeaders() {
        Map<String, String> headers = new HashMap<String, String>(1, 1.0f);
        headers.put("Accept-Language", I18NUtil.getLocale().toString().replace('_', '-'));
        return headers;
    }
}