org.apache.ivory.resource.AbstractEntityManager.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.ivory.resource.AbstractEntityManager.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.ivory.resource;

import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.HashSet;
import java.util.NoSuchElementException;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.Response;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.ObjectUtils;
import org.apache.hadoop.io.IOUtils;
import org.apache.ivory.IvoryException;
import org.apache.ivory.IvoryRuntimException;
import org.apache.ivory.IvoryWebException;
import org.apache.ivory.Pair;
import org.apache.ivory.entity.EntityNotRegisteredException;
import org.apache.ivory.entity.EntityUtil;
import org.apache.ivory.entity.parser.EntityParser;
import org.apache.ivory.entity.parser.EntityParserFactory;
import org.apache.ivory.entity.parser.ValidationException;
import org.apache.ivory.entity.store.ConfigurationStore;
import org.apache.ivory.entity.store.EntityAlreadyExistsException;
import org.apache.ivory.entity.v0.Entity;
import org.apache.ivory.entity.v0.EntityGraph;
import org.apache.ivory.entity.v0.EntityIntegrityChecker;
import org.apache.ivory.entity.v0.EntityType;
import org.apache.ivory.entity.v0.cluster.Cluster;
import org.apache.ivory.resource.APIResult.Status;
import org.apache.ivory.security.CurrentUser;
import org.apache.ivory.util.DeploymentUtil;
import org.apache.ivory.util.RuntimeProperties;
import org.apache.ivory.workflow.WorkflowEngineFactory;
import org.apache.ivory.workflow.engine.WorkflowEngine;
import org.apache.log4j.Logger;

public abstract class AbstractEntityManager {
    private static final Logger LOG = Logger.getLogger(AbstractEntityManager.class);
    private static final Logger AUDIT = Logger.getLogger("AUDIT");
    protected static final int XML_DEBUG_LEN = 10 * 1024;

    private WorkflowEngine workflowEngine;
    protected ConfigurationStore configStore = ConfigurationStore.get();

    public AbstractEntityManager() {
        try {
            workflowEngine = WorkflowEngineFactory.getWorkflowEngine();
        } catch (IvoryException e) {
            throw new IvoryRuntimException(e);
        }
    }

    protected void checkColo(String colo) throws IvoryWebException {
        if (!DeploymentUtil.getCurrentColo().equals(colo)) {
            throw IvoryWebException.newException(
                    "Current colo (" + DeploymentUtil.getCurrentColo() + ") is not " + colo,
                    Response.Status.BAD_REQUEST);
        }
    }

    protected String[] getAllColos() {
        if (DeploymentUtil.isEmbeddedMode())
            return DeploymentUtil.getDefaultColos();
        return RuntimeProperties.get().getProperty("all.colos", DeploymentUtil.getDefaultColo()).split(",");
    }

    protected String[] getColosFromExpression(String coloExpr, String type, String entity) {
        String[] colos;
        if (coloExpr == null || coloExpr.equals("*") || coloExpr.isEmpty()) {
            colos = getApplicableColos(type, entity);
        } else {
            colos = coloExpr.split(",");
        }
        return colos;
    }

    protected String[] getApplicableColos(String type, String name) throws IvoryWebException {
        try {
            if (DeploymentUtil.isEmbeddedMode())
                return DeploymentUtil.getDefaultColos();

            if (EntityType.valueOf(type.toUpperCase()) == EntityType.CLUSTER)
                return getAllColos();

            Entity entity = EntityUtil.getEntity(type, name);
            String[] clusters = EntityUtil.getClustersDefined(entity);
            Set<String> colos = new HashSet<String>();
            for (String cluster : clusters) {
                Cluster clusterEntity = (Cluster) EntityUtil.getEntity(EntityType.CLUSTER, cluster);
                colos.add(clusterEntity.getColo());
            }
            return colos.toArray(new String[colos.size()]);
        } catch (IvoryException e) {
            throw IvoryWebException.newException(e, Response.Status.BAD_REQUEST);
        }
    }

    /**
     * Submit a new entity. Entities can be of type feed, process or data end
     * points. Entity definitions are validated structurally against schema and
     * subsequently for other rules before they are admitted into the system
     * 
     * Entity name acts as the key and an entity once added, can't be added
     * again unless deleted.
     * 
     * @param request
     *            - Servlet Request
     * @param type
     *            - entity type - feed, process or data end point
     * @param colo
     *            - applicable colo
     * @return result of the operation
     */
    public APIResult submit(HttpServletRequest request, String type, String colo) {

        checkColo(colo);
        try {
            audit(request, "STREAMED_DATA", type, "SUBMIT");
            Entity entity = submitInternal(request, type);
            return new APIResult(APIResult.Status.SUCCEEDED,
                    "Submit successful (" + type + ") " + entity.getName());
        } catch (Throwable e) {
            LOG.error("Unable to persist entity object", e);
            throw IvoryWebException.newException(e, Response.Status.BAD_REQUEST);
        }
    }

    /**
     * Post an entity XML with entity type. Validates the XML which can be
     * Process, Feed or Dataendpoint
     * 
     * @param type
     * @return APIResule -Succeeded or Failed
     */
    public APIResult validate(HttpServletRequest request, String type) {
        try {
            EntityType entityType = EntityType.valueOf(type.toUpperCase());
            Entity entity = deserializeEntity(request, entityType);
            validate(entity);
            return new APIResult(APIResult.Status.SUCCEEDED,
                    "Validated successfully (" + entityType + ") " + entity.getName());
        } catch (Throwable e) {
            LOG.error("Validation failed for entity (" + type + ") ", e);
            throw IvoryWebException.newException(e, Response.Status.BAD_REQUEST);
        }
    }

    /**
     * Deletes a scheduled entity, a deleted entity is removed completely from
     * execution pool.
     * 
     * @param type
     * @param entity
     * @return APIResult
     */
    public APIResult delete(HttpServletRequest request, String type, String entity, String colo) {

        checkColo(colo);
        try {
            EntityType entityType = EntityType.valueOf(type.toUpperCase());
            audit(request, entity, type, "DELETE");
            String removedFromEngine = "";
            try {
                Entity entityObj = EntityUtil.getEntity(type, entity);

                canRemove(entityObj);
                if (entityType.isSchedulable() && getWorkflowEngine().isActive(entityObj)) {
                    getWorkflowEngine().delete(entityObj);
                    removedFromEngine = "(KILLED in ENGINE)";
                }

                configStore.remove(entityType, entity);
            } catch (EntityNotRegisteredException e) { // already deleted
                return new APIResult(APIResult.Status.SUCCEEDED,
                        entity + "(" + type + ") doesn't exist. Nothing to do");
            }

            return new APIResult(APIResult.Status.SUCCEEDED,
                    entity + "(" + type + ") removed successfully " + removedFromEngine);
        } catch (Throwable e) {
            LOG.error("Unable to reach workflow engine for deletion or " + "deletion failed", e);
            throw IvoryWebException.newException(e, Response.Status.BAD_REQUEST);
        }
    }

    // Parallel update can get very clumsy if two feeds are updated which
    // are referred by a single process. Sequencing them.
    public synchronized APIResult update(HttpServletRequest request, String type, String entityName, String colo) {

        checkColo(colo);
        try {
            EntityType entityType = EntityType.valueOf(type.toUpperCase());
            audit(request, entityName, type, "UPDATE");
            Entity oldEntity = EntityUtil.getEntity(type, entityName);
            Entity newEntity = deserializeEntity(request, entityType);
            validate(newEntity);

            validateUpdate(oldEntity, newEntity);
            if (!EntityUtil.equals(oldEntity, newEntity)) {
                configStore.initiateUpdate(newEntity);
                getWorkflowEngine().update(oldEntity, newEntity);
                configStore.update(entityType, newEntity);
            }

            return new APIResult(APIResult.Status.SUCCEEDED, entityName + " updated successfully");
        } catch (Throwable e) {
            LOG.error("Updation failed", e);
            throw IvoryWebException.newException(e, Response.Status.BAD_REQUEST);
        } finally {
            ConfigurationStore.get().cleanupUpdateInit();
        }
    }

    private void validateUpdate(Entity oldEntity, Entity newEntity) throws IvoryException {
        if (oldEntity.getEntityType() != newEntity.getEntityType())
            throw new IvoryException(
                    oldEntity.toShortString() + " can't be updated with " + newEntity.toShortString());

        if (oldEntity.getEntityType() == EntityType.CLUSTER)
            throw new IvoryException("Update not supported for clusters");

        String[] props = oldEntity.getEntityType().getImmutableProperties();
        for (String prop : props) {
            Object oldProp, newProp;
            try {
                oldProp = PropertyUtils.getProperty(oldEntity, prop);
                newProp = PropertyUtils.getProperty(newEntity, prop);
            } catch (Exception e) {
                throw new IvoryException(e);
            }
            if (!ObjectUtils.equals(oldProp, newProp))
                throw new ValidationException(oldEntity.toShortString() + ": " + prop + " can't be changed");
        }
    }

    private void canRemove(Entity entity) throws IvoryException {
        Pair<String, EntityType>[] referencedBy = EntityIntegrityChecker.referencedBy(entity);
        if (referencedBy != null && referencedBy.length > 0) {
            StringBuffer messages = new StringBuffer();
            for (Pair<String, EntityType> ref : referencedBy) {
                messages.append(ref).append("\n");
            }
            throw new IvoryException(entity.getName() + "(" + entity.getEntityType() + ") cant "
                    + "be removed as it is referred by " + messages);
        }
    }

    protected synchronized Entity submitInternal(HttpServletRequest request, String type)
            throws IOException, IvoryException {

        EntityType entityType = EntityType.valueOf(type.toUpperCase());
        Entity entity = deserializeEntity(request, entityType);

        Entity existingEntity = configStore.get(entityType, entity.getName());
        if (existingEntity != null) {
            if (EntityUtil.equals(existingEntity, entity))
                return existingEntity;

            throw new EntityAlreadyExistsException(
                    entity.toShortString() + " already registered with configuration store. "
                            + "Can't be submitted again. Try removing before submitting.");
        }

        validate(entity);
        configStore.publish(entityType, entity);
        LOG.info("Submit successful: (" + type + ")" + entity.getName());
        return entity;
    }

    protected Entity deserializeEntity(HttpServletRequest request, EntityType entityType)
            throws IOException, IvoryException {

        EntityParser<?> entityParser = EntityParserFactory.getParser(entityType);
        InputStream xmlStream = request.getInputStream();
        if (xmlStream.markSupported()) {
            xmlStream.mark(XML_DEBUG_LEN); // mark up to debug len
        }
        try {
            return entityParser.parse(xmlStream);
        } catch (IvoryException e) {
            if (LOG.isDebugEnabled() && xmlStream.markSupported()) {
                try {
                    xmlStream.reset();
                    String xmlData = getAsString(xmlStream);
                    LOG.debug("XML DUMP for (" + entityType + "): " + xmlData, e);
                } catch (IOException ignore) {
                }
            }
            throw e;
        }
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    private void validate(Entity entity) throws IvoryException {
        EntityParser entityParser = EntityParserFactory.getParser(entity.getEntityType());
        entityParser.validate(entity);
    }

    private String getAsString(InputStream xmlStream) throws IOException {
        byte[] data = new byte[XML_DEBUG_LEN];
        IOUtils.readFully(xmlStream, data, 0, XML_DEBUG_LEN);
        return new String(data);
    }

    protected void audit(HttpServletRequest request, String entity, String type, String action) {
        if (request == null) {
            return; // this must be internal call from Ivory
        }
        AUDIT.info("Performed " + action + " on " + entity + "(" + type + ") :: " + request.getRemoteHost() + "/"
                + CurrentUser.getUser());
    }

    private enum EntityStatus {
        SUBMITTED, SUSPENDED, RUNNING
    }

    /**
     * Returns the status of requested entity.
     * 
     * @param type
     * @param entity
     * @return String
     */
    public APIResult getStatus(String type, String entity, String colo) {

        checkColo(colo);
        Entity entityObj = null;
        try {
            entityObj = EntityUtil.getEntity(type, entity);
            EntityType entityType = EntityType.valueOf(type.toUpperCase());
            String status;

            if (entityType.isSchedulable()) {
                if (workflowEngine.isActive(entityObj)) {
                    if (workflowEngine.isSuspended(entityObj)) {
                        status = EntityStatus.SUSPENDED.name();
                    } else {
                        status = EntityStatus.RUNNING.name();
                    }
                } else {
                    status = EntityStatus.SUBMITTED.name();
                }
            } else {
                status = EntityStatus.SUBMITTED.name();
            }
            return new APIResult(Status.SUCCEEDED, status);
        } catch (IvoryWebException e) {
            throw e;
        } catch (Exception e) {

            LOG.error("Unable to get status for entity " + entity + "(" + type + ")", e);
            throw IvoryWebException.newException(e, Response.Status.BAD_REQUEST);
        }
    }

    /**
     * Returns dependencies.
     * 
     * @param type
     * @param entity
     * @return EntityList
     */
    public EntityList getDependencies(String type, String entity) {

        try {
            Entity entityObj = EntityUtil.getEntity(type, entity);
            Set<Entity> dependents = EntityGraph.get().getDependents(entityObj);
            Entity[] entities = dependents.toArray(new Entity[dependents.size()]);
            return new EntityList(entities == null ? new Entity[] {} : entities);
        } catch (Exception e) {
            LOG.error("Unable to get dependencies for entity " + entity + "(" + type + ")", e);
            throw IvoryWebException.newException(e, Response.Status.BAD_REQUEST);
        }
    }

    /**
     * Returns the list of entities registered of a given type.
     * 
     * @param type
     * @return String
     */
    public EntityList getDependencies(String type) {
        try {
            EntityType entityType = EntityType.valueOf(type.toUpperCase());
            Collection<String> entityNames = configStore.getEntities(entityType);
            if (entityNames == null || entityNames.equals("")) {
                return new EntityList(new Entity[] {});
            }
            Entity[] entities = new Entity[entityNames.size()];
            int index = 0;
            for (String entityName : entityNames) {
                entities[index++] = configStore.get(entityType, entityName);
            }
            return new EntityList(entities);
        } catch (Exception e) {
            LOG.error("Unable to get list for entities for (" + type + ")", e);
            throw IvoryWebException.newException(e, Response.Status.BAD_REQUEST);
        }
    }

    /**
     * Returns the entity definition as an XML based on name
     * 
     * @param type
     * @param entityName
     * @return String
     */
    public String getEntityDefinition(String type, String entityName) {
        try {
            EntityType entityType = EntityType.valueOf(type.toUpperCase());
            Entity entity = configStore.get(entityType, entityName);
            if (entity == null) {
                throw new NoSuchElementException(entityName + " (" + type + ") not found");
            }
            return entity.toString();
        } catch (Throwable e) {
            LOG.error("Unable to get entity definition from config " + "store for (" + type + ") " + entityName, e);
            throw IvoryWebException.newException(e, Response.Status.BAD_REQUEST);

        }
    }

    protected WorkflowEngine getWorkflowEngine() {
        return this.workflowEngine;
    }
}