nl.b3p.viewer.util.SelectedContentCache.java Source code

Java tutorial

Introduction

Here is the source code for nl.b3p.viewer.util.SelectedContentCache.java

Source

/*
 * Copyright (C) 2013-2017 B3Partners B.V.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package nl.b3p.viewer.util;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.EntityManager;
import javax.servlet.http.HttpServletRequest;
import nl.b3p.viewer.config.ClobElement;
import nl.b3p.viewer.config.app.Application;
import nl.b3p.viewer.config.app.ApplicationLayer;
import nl.b3p.viewer.config.app.Level;
import nl.b3p.viewer.config.app.StartLayer;
import nl.b3p.viewer.config.app.StartLevel;
import nl.b3p.viewer.config.security.Authorizations;
import nl.b3p.viewer.config.services.GeoService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

/**
 *
 * @author Meine Toonen
 */
public class SelectedContentCache {

    private static final Log log = LogFactory.getLog(SelectedContentCache.class);
    public static final String AUTHORIZATIONS_KEY = "authorizations";
    public static final String DETAIL_CACHED_SELECTED_CONTENT = "cachedSelectedContent";
    public static final String DETAIL_CACHED_SELECTED_CONTENT_DIRTY = "cachedSelectedContentDirty";
    public static final String DETAIL_CACHED_EXPANDED_SELECTED_CONTENT = "cachedExpandedSelectedContent";
    public static final String DETAIL_CACHED_EXPANDED_SELECTED_CONTENT_DIRTY = "cachedExpandedSelectedContentDirty";

    public JSONObject getSelectedContent(HttpServletRequest request, Application app, boolean validXmlTags,
            boolean includeAppLayerAttributes, boolean includeRelations, EntityManager em) throws JSONException {
        return getSelectedContent(request, app, validXmlTags, includeAppLayerAttributes, includeRelations, em,
                true);
    }

    public JSONObject getSelectedContent(HttpServletRequest request, Application app, boolean validXmlTags,
            boolean includeAppLayerAttributes, boolean includeRelations, EntityManager em,
            boolean shouldProcessCache) throws JSONException {

        // Don't use cache when any of these parameters is true, cache only
        // the JSON variant used when starting up the viewer
        boolean useExpanded = includeAppLayerAttributes || includeRelations;

        JSONObject cached = null;
        if (mustCreateNewCache(app, validXmlTags, useExpanded)) {
            cached = createSelectedContent(app, validXmlTags, includeAppLayerAttributes, includeRelations, em);
            if (!validXmlTags) {
                ClobElement el = new ClobElement(cached.toString());
                app.getDetails().put(
                        useExpanded ? DETAIL_CACHED_EXPANDED_SELECTED_CONTENT : DETAIL_CACHED_SELECTED_CONTENT, el);
                setApplicationCacheDirty(app, false, useExpanded, em);
                em.getTransaction().commit();
            }
        } else {
            ClobElement el = app.getDetails()
                    .get(useExpanded ? DETAIL_CACHED_EXPANDED_SELECTED_CONTENT : DETAIL_CACHED_SELECTED_CONTENT);
            cached = new JSONObject(el.getValue());
        }
        if (shouldProcessCache) {
            JSONObject selectedContent = processCache(request, cached, em);
            return selectedContent;
        } else {
            return cached;
        }
    }

    private JSONObject processCache(HttpServletRequest request, JSONObject cached, EntityManager em)
            throws JSONException {
        Set<String> roles = Authorizations.getRoles(request, em);

        JSONObject levels = cached.getJSONObject("levels");
        JSONObject appLayers = cached.getJSONObject("appLayers");
        JSONArray selectedContent = cached.getJSONArray("selectedContent");
        JSONObject services = cached.has("services") ? cached.getJSONObject("services") : new JSONObject();

        // sortedKeys() was removed form the api in https://github.com/stleary/JSON-java/commit/9a0471d5a100f6cfb253db52353a2595f5866582
        // not sure if sorting is relevant here, but it was doing 
        // TreeSet keySet = new TreeSet(appLayers.keySet());
        // so if sorting is required we can do
        // for (Iterator<String> it = new TreeSet(appLayers.keySet()).iterator(); it.hasNext();) {
        for (Iterator<String> it = appLayers.keys(); it.hasNext();) {
            String key = it.next();
            JSONObject appLayer = appLayers.getJSONObject(key);
            boolean allowed = isAppLayerAllowed(appLayer, roles);
            if (!allowed) {
                it.remove();
            }
        }

        for (Iterator it = levels.keys(); it.hasNext();) {
            String key = (String) it.next();
            JSONObject level = levels.getJSONObject(key);
            boolean allowed = isLevelAllowed(level, roles);
            if (!allowed) {
                it.remove();
            }
            JSONArray newLayers = new JSONArray();
            if (level.has("layers")) {
                JSONArray layers = level.getJSONArray("layers");
                for (int i = 0; i < layers.length(); i++) {
                    String layerId = layers.getString(i);
                    if (appLayers.has(layerId)) {
                        newLayers.put(layerId);
                    }
                }
                level.put("layers", newLayers);
            }
        }

        String scheme = request.getScheme();
        String serverName = request.getServerName();
        int serverPort = request.getServerPort();
        String contextPath = request.getContextPath();
        StringBuilder url = new StringBuilder();
        String servletPath = "/action/proxy/wms";
        url.append(scheme).append("://").append(serverName);

        if ((serverPort != 80) && (serverPort != 443)) {
            url.append(":").append(serverPort);
        }

        url.append(contextPath).append(servletPath);
        final String proxyUrl = url.toString();
        for (Iterator<String> it = services.keys(); it.hasNext();) {

            String key = it.next();
            JSONObject service = services.getJSONObject(key);
            if (service.has(GeoService.DETAIL_USE_PROXY) && service.getBoolean(GeoService.DETAIL_USE_PROXY)) {
                try {
                    String actualURL = service.getString("url");
                    String param = URLEncoder.encode(actualURL, "UTF-8");
                    StringBuilder newUrl = new StringBuilder(proxyUrl);
                    newUrl.append("?url=");
                    newUrl.append(param);
                    if (service.has(GeoService.PARAM_MUST_LOGIN)
                            && service.getBoolean(GeoService.PARAM_MUST_LOGIN)) {
                        newUrl.append("&mustLogin=true&serviceId=");
                        newUrl.append(service.get("id"));
                    }
                    service.put("url", newUrl);
                } catch (UnsupportedEncodingException ex) {
                    log.error("Cannot add proxy url for service: ", ex);
                }
            }
        }

        JSONArray newSelectedContent = new JSONArray();
        for (int i = 0; i < selectedContent.length(); i++) {
            JSONObject obj = selectedContent.getJSONObject(i);
            String type = obj.getString("type");
            String id = obj.getString("id");
            if (type.equalsIgnoreCase("level")) {
                if (isLevelAllowed(id, levels)) {
                    newSelectedContent.put(obj);
                }
            }
        }

        cached.put("selectedContent", newSelectedContent);
        return cached;
    }

    private boolean isLevelAllowed(String id, JSONObject levels) {
        return levels.has(id);
    }

    private boolean isLevelAllowed(JSONObject level, Set<String> roles) throws JSONException {
        boolean allowed = isAuthorized(level, roles, false);
        if (!allowed) {
            return false;
        }

        if (level.has("children")) {
            JSONArray children = level.getJSONArray("children");
            JSONArray newChildren = new JSONArray();
            for (int i = 0; i < children.length(); i++) {
                JSONObject child = children.getJSONObject(i);
                if (isLevelAllowed(child, roles)) {
                    newChildren.put(child.getString("child"));
                }
            }
            level.put("children", newChildren);
        }
        return true;
    }

    private boolean isAppLayerAllowed(JSONObject appLayer, Set<String> roles) throws JSONException {
        boolean allowed = isAuthorized(appLayer, roles, false);
        if (!allowed) {
            return false;
        }

        boolean editAuthorized = isAuthorized(appLayer, roles, true, "editAuthorizations");
        appLayer.put("editAuthorized", editAuthorized);

        return true;
    }

    private boolean isAuthorized(JSONObject obj, Set<String> roles, boolean alsoWriters) throws JSONException {
        return isAuthorized(obj, roles, alsoWriters, AUTHORIZATIONS_KEY);
    }

    private boolean isAuthorized(JSONObject obj, Set<String> roles, boolean alsoWriters, String authString)
            throws JSONException {

        if (obj.has(authString) && obj.getJSONObject(authString).length() != 0) {
            // Levels only have readers
            JSONArray readers = obj.getJSONObject(authString).getJSONArray("readers");
            if (readers.length() > 0) {
                Set<String> allowedRoles = new HashSet();
                for (int i = 0; i < readers.length(); i++) {
                    if (!readers.isNull(i)) {
                        String reader = readers.getString(i);
                        allowedRoles.add(reader);
                    }
                }

                if (Collections.disjoint(roles, allowedRoles)) {
                    return false;
                }
            }

            if (alsoWriters) {
                JSONArray writers = obj.getJSONObject(authString).getJSONArray("writers");
                if (writers.length() > 0) {
                    Set<String> allowedRoles = new HashSet();
                    for (int i = 0; i < writers.length(); i++) {
                        String writer = writers.getString(i);
                        allowedRoles.add(writer);
                    }

                    if (Collections.disjoint(roles, allowedRoles)) {
                        return false;
                    }
                }
            }
        }
        obj.remove(authString);
        return true;
    }

    public JSONObject createSelectedContent(Application app, boolean validXmlTags,
            boolean includeAppLayerAttributes, boolean includeRelations, EntityManager em) throws JSONException {
        Level root = app.getRoot();
        JSONObject o = new JSONObject();
        if (root != null) {
            o.put("rootLevel", root.getId().toString());

            Application.TreeCache treeCache = app.loadTreeCache(em);
            treeCache.initializeLevels("left join fetch l.documents", em);
            treeCache.initializeApplicationLayers("left join fetch al.details", em);
            Authorizations.ApplicationCache appCache = Authorizations.getApplicationCache(app, em);

            JSONObject levels = new JSONObject();
            o.put("levels", levels);
            JSONObject appLayers = new JSONObject();
            o.put("appLayers", appLayers);
            JSONArray selectedContent = new JSONArray();
            o.put("selectedContent", selectedContent);

            List selectedObjects = new ArrayList();
            walkAppTreeForJSON(levels, appLayers, selectedObjects, root, false, validXmlTags,
                    includeAppLayerAttributes, includeRelations, app, treeCache, appCache, em, false);

            Collections.sort(selectedObjects, new Comparator() {
                @Override
                public int compare(Object lhs, Object rhs) {
                    Integer lhsIndex, rhsIndex;
                    if (lhs instanceof StartLevel) {
                        lhsIndex = ((StartLevel) lhs).getSelectedIndex();
                    } else {
                        lhsIndex = ((StartLayer) lhs).getSelectedIndex();
                    }
                    if (rhs instanceof StartLevel) {
                        rhsIndex = ((StartLevel) rhs).getSelectedIndex();
                    } else {
                        rhsIndex = ((StartLayer) rhs).getSelectedIndex();
                    }
                    return lhsIndex.compareTo(rhsIndex);
                }
            });
            for (Object obj : selectedObjects) {
                JSONObject j = new JSONObject();
                if (obj instanceof StartLevel) {
                    j.put("type", "level");
                    j.put("id", ((StartLevel) obj).getLevel().getId().toString());
                } else {
                    j.put("type", "appLayer");
                    j.put("id", ((StartLayer) obj).getApplicationLayer().getId().toString());
                }
                selectedContent.put(j);
            }

            Map<GeoService, Set<String>> usedLayersByService = new HashMap<GeoService, Set<String>>();
            visitLevelForUsedServicesLayers(root, usedLayersByService, app, treeCache);

            if (!usedLayersByService.isEmpty()) {
                JSONObject services = new JSONObject();
                o.put("services", services);
                for (Map.Entry<GeoService, Set<String>> entry : usedLayersByService.entrySet()) {
                    GeoService gs = entry.getKey();
                    Set<String> usedLayers = entry.getValue();
                    String serviceId = gs.getId().toString();
                    if (validXmlTags) {
                        serviceId = "service_" + serviceId;
                    }
                    services.put(serviceId, gs.toJSONObject(false, usedLayers, validXmlTags, true, em));
                }
            }
        }
        return o;
    }

    private void walkAppTreeForJSON(JSONObject levels, JSONObject appLayers, List selectedContent, Level l,
            boolean parentIsBackground, boolean validXmlTags, boolean includeAppLayerAttributes,
            boolean includeRelations, Application app, Application.TreeCache treeCache,
            Authorizations.ApplicationCache appCache, EntityManager em, boolean previouslySelected)
            throws JSONException {

        StartLevel sl = l.getStartLevels().get(app);
        if (sl != null) {
            JSONObject o = l.toJSONObject(false, app, null, em);

            Authorizations.Read auths = appCache.getProtectedLevels().get(l.getId());
            o.put(AUTHORIZATIONS_KEY, auths != null ? auths.toJSON() : new JSONObject());
            o.put("background", l.isBackground() || parentIsBackground);
            o.put("removed", sl.isRemoved());
            String levelId = l.getId().toString();
            if (validXmlTags) {
                levelId = "level_" + levelId;
            }
            levels.put(levelId, o);
            boolean selected = false || previouslySelected;

            if (sl != null && sl.getSelectedIndex() != null && !sl.isRemoved() && !previouslySelected) {
                selectedContent.add(sl);
                selected = true;
            }

            for (ApplicationLayer al : l.getLayers()) {
                StartLayer startLayer = al.getStartLayers().get(app);
                if (startLayer != null) {
                    JSONObject p = al.toJSONObject(includeAppLayerAttributes, includeRelations, em, app);
                    p.put("background", l.isBackground() || parentIsBackground);
                    p.put("removed", startLayer.isRemoved());

                    Authorizations.ReadWrite rw = appCache.getProtectedAppLayers().get(al.getId());
                    p.put("editAuthorizations", rw != null ? rw.toJSON() : new JSONObject());
                    String alId = al.getId().toString();
                    if (validXmlTags) {
                        alId = "appLayer_" + alId;
                    }

                    Authorizations.ReadWrite applayerAuths = appCache.getProtectedAppLayers().get(al.getId());
                    p.put(AUTHORIZATIONS_KEY, applayerAuths != null ? applayerAuths.toJSON() : new JSONObject());

                    appLayers.put(alId, p);

                    if (startLayer.getSelectedIndex() != null && !startLayer.isRemoved()) {
                        selectedContent.add(startLayer);
                    }
                }
            }

            List<Level> children = treeCache.getChildrenByParent().get(l);
            if (children != null) {
                Collections.sort(children);
                JSONArray jsonChildren = new JSONArray();
                o.put("children", jsonChildren);
                for (Level child : children) {
                    JSONObject childObject = new JSONObject();
                    String childId = child.getId().toString();
                    if (validXmlTags) {
                        childId = "level_" + childId;
                    }
                    childObject.put("child", childId);
                    Authorizations.Read levelAuths = appCache.getProtectedLevels().get(child.getId());
                    childObject.put(AUTHORIZATIONS_KEY,
                            levelAuths != null ? levelAuths.toJSON() : new JSONObject());
                    jsonChildren.put(childObject);
                    walkAppTreeForJSON(levels, appLayers, selectedContent, child, l.isBackground(), validXmlTags,
                            includeAppLayerAttributes, includeRelations, app, treeCache, appCache, em, selected);
                }
            }
        }
    }

    private void visitLevelForUsedServicesLayers(Level l, Map<GeoService, Set<String>> usedLayersByService,
            Application app, Application.TreeCache treeCache) {
        for (ApplicationLayer al : l.getLayers()) {
            GeoService gs = al.getService();

            Set<String> usedLayers = usedLayersByService.get(gs);
            if (usedLayers == null) {
                usedLayers = new HashSet<String>();
                usedLayersByService.put(gs, usedLayers);
            }
            usedLayers.add(al.getLayerName());
        }
        List<Level> children = treeCache.getChildrenByParent().get(l);
        if (children != null) {
            for (Level child : children) {
                visitLevelForUsedServicesLayers(child, usedLayersByService, app, treeCache);
            }
        }
    }

    private boolean mustCreateNewCache(Application app, boolean validXmlTags, boolean expanded) {
        ClobElement cache = expanded ? app.getDetails().get(DETAIL_CACHED_EXPANDED_SELECTED_CONTENT)
                : app.getDetails().get(DETAIL_CACHED_SELECTED_CONTENT);
        if (cache == null || validXmlTags) {
            return true;
        } else {
            ClobElement dirtyClob = expanded ? app.getDetails().get(DETAIL_CACHED_EXPANDED_SELECTED_CONTENT_DIRTY)
                    : app.getDetails().get(DETAIL_CACHED_SELECTED_CONTENT_DIRTY);
            boolean dirty = Boolean.valueOf(dirtyClob.getValue());
            return dirty;
        }
    }

    public static void setApplicationCacheDirty(Application app, Boolean dirty, Boolean expanded,
            EntityManager em) {
        setApplicationCacheDirty(app, dirty, expanded, false, em);
    }

    public static void setApplicationCacheDirty(Application app, Boolean dirty, Boolean expanded,
            Boolean onlyThisApplication, EntityManager em) {
        Set<Application> apps = new HashSet<Application>();
        if (dirty && !onlyThisApplication) {
            apps = app.getRoot().findApplications(em);
        } else {
            apps.add(app);
        }
        // Also invalidate possible mashups
        for (Application application : apps) {
            if (dirty) {
                application.getDetails().put(DETAIL_CACHED_SELECTED_CONTENT_DIRTY,
                        new ClobElement(dirty.toString()));
                application.getDetails().put(DETAIL_CACHED_EXPANDED_SELECTED_CONTENT_DIRTY,
                        new ClobElement(dirty.toString()));
            } else {
                application.getDetails().put(expanded ? DETAIL_CACHED_EXPANDED_SELECTED_CONTENT_DIRTY
                        : DETAIL_CACHED_SELECTED_CONTENT_DIRTY, new ClobElement(dirty.toString()));
            }
        }
    }
}