org.agatom.springatom.cmp.action.DefaultActionsModelReader.java Source code

Java tutorial

Introduction

Here is the source code for org.agatom.springatom.cmp.action.DefaultActionsModelReader.java

Source

/**************************************************************************************************
 * This file is part of [SpringAtom] Copyright [kornicameister@gmail.com][2014]                   *
 *                                                                                                *
 * [SpringAtom] is free software: you can redistribute it and/or modify                           *
 * it under the terms of the GNU General Public License as published by                           *
 * the Free Software Foundation, either version 3 of the License, or                              *
 * (at your option) any later version.                                                            *
 *                                                                                                *
 * [SpringAtom] 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 Public License for more details.                                                   *
 *                                                                                                *
 * You should have received a copy of the GNU General Public License                              *
 * along with [SpringAtom].  If not, see <http://www.gnu.org/licenses/gpl.html>.                  *
 **************************************************************************************************/

package org.agatom.springatom.cmp.action;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.fasterxml.jackson.databind.node.MissingNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import javassist.NotFoundException;
import org.agatom.springatom.cmp.action.model.AbstractAction;
import org.agatom.springatom.cmp.action.model.Action;
import org.agatom.springatom.cmp.action.model.ActionModel;
import org.agatom.springatom.cmp.action.model.actions.ViewAction;
import org.agatom.springatom.cmp.action.model.security.ActionRole;
import org.agatom.springatom.cmp.action.model.security.ActionRoleMap;
import org.agatom.springatom.cmp.action.model.security.ActionSecurityCheck;
import org.agatom.springatom.cmp.action.model.security.DefaultActionRole;
import org.agatom.springatom.cmp.action.reader.ActionsModelReader;
import org.agatom.springatom.core.locale.SMessageSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.*;

import javax.annotation.PostConstruct;
import java.io.*;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * <small>Class is a part of <b>SpringAtom</b> and was created at 29.03.14</small>
 *
 * @author kornicameister
 * @version 0.0.1
 * @since 0.0.1
 */
class DefaultActionsModelReader implements ActionsModelReader {
    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultActionsModelReader.class);
    private static final String ACTION_MODELS_KEY = "actionModels";
    private static final String RESOURCE_BUNDLE_KEY = "resourceBundle";
    private String modelFile = null;
    private Boolean parseOnLoad = false;
    private JsonNode actionModel = null;
    private ObjectMapper objectMapper = null;
    private Map<String, ActionModelReferenceMap> flattenActionModel = null;
    private SMessageSource messageSource = null;

    /** {@inheritDoc} */
    @Override
    public Set<ActionModel> getActionModels() {
        return null;
    }

    /** {@inheritDoc} */
    @Override
    public ActionModel getActionModel(final String name) throws NotFoundException {
        LOGGER.debug(String.format("getActionModel(name=%s)", name));
        final ActionModelReferenceMap map = flattenActionModel.get(name);
        if (map == null) {
            throw new NotFoundException(String.format("No action model found for key=%s", name));
        }
        try {
            final long startTime = System.nanoTime();
            final ActionModel model = new ActionModel()
                    .setContent(this.resolveContent(map, Sets.<Action>newTreeSet()));

            JsonNode tmpNode;
            if ((tmpNode = map.node.findValue("description")) != null) {
                model.setDescription(tmpNode.asText());
            }
            if ((tmpNode = map.node.findValue("name")) != null) {
                model.setName(tmpNode.asText());
            }

            final long endTime = System.nanoTime() - startTime;

            LOGGER.trace(String.format("getActionModel(name=%s) took %d ms", name,
                    TimeUnit.NANOSECONDS.toMillis(endTime)));

            return model;
        } catch (Exception ignore) {

        }
        return null;
    }

    private Set<Action> resolveContent(final ActionModelReferenceMap map, final Set<Action> actions) {
        if (!map.actions.isEmpty()) {
            for (final JsonNode node : map.actions) {
                actions.add(this.resolveFromAction((ObjectNode) node));
            }
        }
        if (!map.submodel.isEmpty()) {
            for (final String subModelKey : map.submodel.keySet()) {

                final ActionModelReferenceMap actionModel = this.flattenActionModel.get(subModelKey);
                final Set<Action> actionModelActions = this.resolveContent(actionModel,
                        Sets.<Action>newLinkedHashSet());

                final ActionModel model = new ActionModel();
                model.setName(actionModel.node.findValue("name").textValue());
                model.setDescription(actionModel.node.findValue("description").textValue());
                model.setContent(actionModelActions);
                model.setLabel(this.getLabel(actionModel.node.findValue(RESOURCE_BUNDLE_KEY).textValue()));

                actions.add(model);
            }
        }
        return actions;
    }

    private String getLabel(String name) {
        return this.messageSource.getMessage(name, LocaleContextHolder.getLocale());
    }

    private Action resolveFromAction(final ObjectNode node) {
        final String type = node.get("type").textValue();
        AbstractAction action = null;
        switch (type) {
        case "view": {
            action = new ViewAction();
        }
        }

        if (action != null) {

            action.setName(node.get("name").textValue());
            action.setLabel(this.getLabel(node.get(RESOURCE_BUNDLE_KEY).textValue()));

            this.setCssRule(node, action);
            this.setSecurity(node, action);

            action.setOrder(node.has("index") ? node.get("index").shortValue() : -1);
        } else {
            throw new IllegalStateException(String.format("%s is not supported", type));
        }

        return action;
    }

    private void setSecurity(final ObjectNode node, final AbstractAction action) {
        final boolean isSecurityEnabled = node.has("security");
        if (isSecurityEnabled) {
            final JsonNode security = node.get("security");

            if (security.size() == 0) {
                action.disableSecurity();
                return;
            }

            boolean authenticated = security.has("authenticated") && security.get("authenticated").asBoolean(false);
            boolean hasRoles = security.has("roles");

            authenticated = authenticated | hasRoles;

            final ActionSecurityCheck check = new ActionSecurityCheck();
            check.setAuthenticated(authenticated);

            if (hasRoles) {
                final ActionRoleMap.Connector[] connectors = ActionRoleMap.Connector.values();
                final JsonNode roles = security.get("roles");
                check.setRoles(this.resolveSecurityRoles(roles, connectors));
            }

            action.setSecurity(check);

        } else {
            action.disableSecurity();
        }
    }

    private ActionRole resolveSecurityRoles(final JsonNode roles, final ActionRoleMap.Connector[] connectors) {
        if (JsonNodeType.STRING.equals(roles.getNodeType())) {
            return new DefaultActionRole().appendRole(roles.asText());
        }
        final Iterator<JsonNode> iterator = roles.iterator();
        ActionRole actionRole = null;

        while (iterator.hasNext()) {
            final JsonNode node = iterator.next();
            final JsonNodeType type = node.getNodeType();
            if (JsonNodeType.STRING.equals(type)) {
                actionRole = new DefaultActionRole().appendRole(node.asText());
            } else if (JsonNodeType.ARRAY.equals(type)) {

                final ArrayNode arrayNode = (ArrayNode) node;
                final DefaultActionRole role = new DefaultActionRole();
                for (JsonNode textRoleNode : arrayNode) {
                    role.appendRole(textRoleNode.asText());
                }
                actionRole = role;

            } else if (JsonNodeType.OBJECT.equals(type)) {

                final ActionRoleMap actionRoleMap = new ActionRoleMap();
                final Map<ActionRoleMap.Connector, DefaultActionRole> map = Maps
                        .newHashMapWithExpectedSize(node.size());
                String strConnector;
                for (final ActionRoleMap.Connector connector : connectors) {
                    strConnector = connector.toString().toLowerCase();
                    if (node.has(strConnector)) {
                        final DefaultActionRole role = new DefaultActionRole();
                        for (JsonNode textRoleNode : node.get(strConnector)) {
                            role.appendRole(textRoleNode.asText());
                        }
                        map.put(connector, role);
                    }
                }
                actionRoleMap.setRoles(map);
                actionRole = actionRoleMap;

            }
        }

        return actionRole;
    }

    private void setCssRule(final ObjectNode node, final AbstractAction action) {
        if (node.has("iconClass")) {
            action.setIconClass(node.get("iconClass").textValue());
        }
        if (node.has("btnClass")) {
            action.setBtnClass(node.get("btnClass").textValue());
        }
    }

    @PostConstruct
    private void initializeFileRead() throws Exception {
        String pathToUse = this.modelFile;
        if (pathToUse.startsWith(ResourceUtils.CLASSPATH_URL_PREFIX)) {
            pathToUse = pathToUse.replace(ResourceUtils.CLASSPATH_URL_PREFIX, "");
        }
        try {
            final InputStream file = new ClassPathResource(this.modelFile).getInputStream();
            if (file.available() > 1) {
                final String stringContent = FileCopyUtils
                        .copyToString(new BufferedReader(new InputStreamReader(file)));
                if (StringUtils.hasText(stringContent)) {
                    this.actionModel = this.objectMapper.readTree(stringContent);
                } else {
                    throw new Exception("Invalid content");
                }
            }
        } catch (FileNotFoundException exp) {
            LOGGER.error(String.format("Could not locate action-model file at %s", pathToUse), exp);
        } catch (IOException exp) {
            LOGGER.error(String.format("Could not read action-model file at %s", pathToUse), exp);
        } catch (Exception exp) {
            LOGGER.error(String.format("Generic exception when reading => %s", exp.getMessage()));
        }
        if (this.parseOnLoad) {
            // TODO add appropriate exception when actionModel is null
            this.parseActionModels();
        }
    }

    private void parseActionModels() throws Exception {
        Assert.notNull(this.actionModel);
        LOGGER.trace("Starting parsing actionModel");
        try {
            final long startTime = System.nanoTime();
            {
                final JsonNode actionModelsNode = this.actionModel.get(ACTION_MODELS_KEY);
                Assert.isTrue(actionModelsNode.getNodeType().equals(JsonNodeType.ARRAY));

                final ArrayNode actionModels = (ArrayNode) actionModelsNode;
                final int length = actionModels.size();
                LOGGER.trace(String.format("%d actionModels found", length));

                final Map<String, ActionModelReferenceMap> referenceMap = Maps.newHashMap();
                for (final JsonNode node : actionModels) {
                    referenceMap.put(node.get("name").textValue(), this.flattenActionModels((ObjectNode) node));
                }
                this.flattenActionModel = referenceMap;
            }
            LOGGER.info(String.format("Loaded actionModel in %dms",
                    TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)));
        } catch (Exception exp) {
            LOGGER.error("Error in parsing actionModel", exp);
            throw exp;
        }
    }

    private ActionModelReferenceMap flattenActionModels(final ObjectNode object) throws Exception {
        Assert.notNull(object, "ActionModel can not be null");

        final ActionModelReferenceMap map = new ActionModelReferenceMap();
        map.node = object;

        if (this.hasSubmodels(object)) {
            final ArrayNode submodels = (ArrayNode) object.get("submodels");
            for (final JsonNode node : submodels) {
                map.submodel.put(node.textValue(), node);
            }
        }
        if (this.hasActions(object)) {
            final ArrayNode actions = (ArrayNode) object.get("actions");
            for (final JsonNode action : actions) {
                map.actions.add(action);
            }
        }

        return map;
    }

    private boolean hasSubmodels(final JsonNode next) {
        return !ClassUtils.isAssignableValue(MissingNode.class, next.findPath("submodels"));
    }

    private boolean hasActions(final JsonNode next) {
        return !ClassUtils.isAssignableValue(MissingNode.class, next.findPath("actions"));
    }

    /**
     * <p>Setter for the field <code>modelFile</code>.</p>
     *
     * @param modelFile a {@link String} object.
     */
    public void setModelFile(final String modelFile) {
        Assert.hasText(modelFile, "modelFile has no text");
        LOGGER.trace(String.format("Setting modelFile => %s", modelFile));
        this.modelFile = modelFile;
    }

    /**
     * <p>Setter for the field <code>parseOnLoad</code>.</p>
     *
     * @param parseOnLoad a {@link Boolean} object.
     */
    public void setParseOnLoad(final Boolean parseOnLoad) {
        LOGGER.trace(String.format("Setting parseOnLoad => %s", parseOnLoad));
        this.parseOnLoad = parseOnLoad;
    }

    /**
     * <p>Setter for the field <code>objectMapper</code>.</p>
     *
     * @param objectMapper a {@link com.fasterxml.jackson.databind.ObjectMapper} object.
     */
    public void setObjectMapper(final ObjectMapper objectMapper) {
        Assert.notNull(objectMapper, "objectMapper has no text");
        LOGGER.trace(String.format("Setting objectMapper => %s", objectMapper));
        this.objectMapper = objectMapper;
    }

    /**
     * <p>Setter for the field <code>messageSource</code>.</p>
     *
     * @param messageSource a {@link org.agatom.springatom.core.locale.SMessageSource} object.
     */
    public void setMessageSource(final SMessageSource messageSource) {
        Assert.notNull(messageSource, "messageSource has no text");
        LOGGER.trace(String.format("Setting messageSource => %s", messageSource));
        this.messageSource = messageSource;
    }

    private class ActionModelReferenceMap {
        private ObjectNode node = null;
        private Set<JsonNode> actions = Sets.newHashSet();
        private Map<String, JsonNode> submodel = Maps.newHashMap();
    }
}