Package org.apache.ivory.entity.store

Source Code of org.apache.ivory.entity.store.ConfigurationStore

/*
* 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.entity.store;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import javax.xml.bind.JAXBException;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.ivory.IvoryException;
import org.apache.ivory.entity.v0.Entity;
import org.apache.ivory.entity.v0.EntityType;
import org.apache.ivory.service.ConfigurationChangeListener;
import org.apache.ivory.service.IvoryService;
import org.apache.ivory.util.ReflectionUtils;
import org.apache.ivory.util.StartupProperties;
import org.apache.log4j.Logger;

public class ConfigurationStore implements IvoryService {

    private static final Logger LOG = Logger.getLogger(ConfigurationStore.class);
    private static final Logger AUDIT = Logger.getLogger("AUDIT");
    private static final String UTF_8 = "UTF-8";

    private static final ConfigurationStore store = new ConfigurationStore();

    private Set<ConfigurationChangeListener> listeners = new LinkedHashSet<ConfigurationChangeListener>();

    private ThreadLocal<Entity> updatesInProgress = new ThreadLocal<Entity>();

    public static ConfigurationStore get() {
        return store;
    }

    private final Map<EntityType, ConcurrentHashMap<String, Entity>> dictionary = new HashMap<EntityType, ConcurrentHashMap<String, Entity>>();

    private final FileSystem fs;
    private final Path storePath;

    private static final Entity NULL = new Entity() {
        @Override
        public String getName() {
            return "NULL";
        }
    };

    @SuppressWarnings("unchecked")
    private ConfigurationStore() {
        Class<? extends Entity>[] entityClasses = new Class[EntityType.values().length];

        int index = 0;

        for (EntityType type : EntityType.values()) {
            dictionary.put(type, new ConcurrentHashMap<String, Entity>());
            entityClasses[index++] = type.getEntityClass();
        }

        String uri = StartupProperties.get().getProperty("config.store.uri");
        storePath = new Path(uri);
        try {
            fs = FileSystem.get(storePath.toUri(), new Configuration());
        } catch (Exception e) {
            throw new RuntimeException("Unable to bring up config store", e);
        }
    }

    @Override
    public void init() throws IvoryException {
        String listenerClassNames = StartupProperties.get().
                getProperty("configstore.listeners", "org.apache.ivory.entity.v0.EntityGraph");
        for(String listenerClassName:listenerClassNames.split(",")) {
            listenerClassName = listenerClassName.trim();
            if (listenerClassName.isEmpty()) continue;
            ConfigurationChangeListener listener = ReflectionUtils.getInstanceByClassName(listenerClassName);
            registerListener(listener);
        }
       
        try {
            for (EntityType type : EntityType.values()) {
                ConcurrentHashMap<String, Entity> entityMap = dictionary.get(type);
                FileStatus[] files = fs.globStatus(new Path(storePath, type.name() + Path.SEPARATOR + "*"));
                if (files != null) {
                    for (FileStatus file : files) {
                        String fileName = file.getPath().getName();
                        String encodedEntityName = fileName.substring(0, fileName.length() - 4); // drop
                                                                                                 // ".xml"
                        String entityName = URLDecoder.decode(encodedEntityName, UTF_8);
                        Entity entity = restore(type, entityName);
                        entityMap.put(entityName, entity);
                        onAdd(entity);
                    }
                }
            }
        } catch (IOException e) {
            throw new IvoryException("Unable to restore configurations", e);
        }
    }

    public void registerListener(ConfigurationChangeListener listener) {
        listeners.add(listener);
    }

    /**
     *
     * @param type
     *            - EntityType that need to be published
     * @param entity
     *            - Reference to the Entity Object
     * @throws IvoryException
     */
    public synchronized void publish(EntityType type, Entity entity) throws IvoryException {
        try {
            if (get(type, entity.getName()) == null) {
                persist(type, entity);
                dictionary.get(type).put(entity.getName(), entity);
                onAdd(entity);
            } else {
                throw new EntityAlreadyExistsException(entity.toShortString() + " already registered with configuration store. "
                        + "Can't be submitted again. Try removing before submitting.");
            }
        } catch (IOException e) {
            throw new StoreAccessException(e);
        }
        AUDIT.info(type + "/" + entity.getName() + " is published into config store");
    }

    private synchronized void updateInternal(EntityType type, Entity entity) throws IvoryException {
        try {
            if (get(type, entity.getName()) != null) {
                persist(type, entity);
                Entity oldEntity = dictionary.get(type).put(entity.getName(), entity);
                onChange(oldEntity, entity);
            } else {
                throw new IvoryException(entity.toShortString() + " doesn't exist");
            }
        } catch (IOException e) {
            throw new StoreAccessException(e);
        }
        AUDIT.info(type + "/" + entity.getName() + " is replaced into config store");
    }

    public synchronized void rollbackUpdate(EntityType type, Entity entity) throws IvoryException {
        updateInternal(type, entity);
    }

    public synchronized void update(EntityType type, Entity entity) throws IvoryException {
        if (updatesInProgress.get() == entity) {
            updateInternal(type, entity);
        } else {
            throw new IvoryException(entity.toShortString() + " is not initialized for update");
        }
    }

    private void onAdd(Entity entity) {
        for (ConfigurationChangeListener listener : listeners) {
            try {
                listener.onAdd(entity);
            } catch (Throwable e) {
                LOG.warn("Encountered exception while notifying " + listener + entity.toShortString(), e);
            }
        }
    }

    private void onChange(Entity oldEntity, Entity newEntity) throws IvoryException {
        for (ConfigurationChangeListener listener : listeners) {
      listener.onChange(oldEntity, newEntity);
        }
    }

    public synchronized void initiateUpdate(Entity entity) throws IvoryException {
        if (get(entity.getEntityType(), entity.getName()) == null || updatesInProgress.get() != null) {
            throw new IvoryException("An update for " + entity.toShortString() + " is already in progress or doesn't exist");
        }
        updatesInProgress.set(entity);
    }

    /**
     *
     * @param type
     *            - Entity type that is being retrieved
     * @param name
     *            - Name as it appears in the entity xml definition
     * @param <T>
     *            - Actual Entity object type
     * @return - Entity object from internal dictionary, If the object is not
     *         loaded in memory yet, it will retrieve it from persistent store
     *         just in time. On startup all the entities will be added to the
     *         dictionary with null reference.
     * @throws IvoryException
     */
    @SuppressWarnings("unchecked")
    public <T extends Entity> T get(EntityType type, String name) throws IvoryException {
        ConcurrentHashMap<String, Entity> entityMap = dictionary.get(type);
        if (entityMap.containsKey(name)) {
            if (updatesInProgress.get() != null && updatesInProgress.get().getEntityType() == type
                    && updatesInProgress.get().getName().equals(name)) {
                return (T) updatesInProgress.get();
            }
            T entity = (T) entityMap.get(name);
            if (entity == NULL) { // Object equality being checked
                try {
                    entity = this.<T>restore(type, name);
                } catch (IOException e) {
                    throw new StoreAccessException(e);
                }
                entityMap.put(name, entity);
                return entity;
            } else {
                return entity;
            }
        } else {
            return null;
        }
    }

    public Collection<String> getEntities(EntityType type) {
        return Collections.unmodifiableCollection(dictionary.get(type).keySet());
    }

    /**
     * Remove an entity which is already stored in the config store
     *
     * @param type
     *            - Entity type being removed
     * @param name
     *            - Name of the entity object being removed
     * @return - True is remove is successful, false if request entity doesn't
     *         exist
     * @throws IvoryException
     */
    public boolean remove(EntityType type, String name) throws IvoryException {
        Map<String, Entity> entityMap = dictionary.get(type);
        if (entityMap.containsKey(name)) {
            try {
                archive(type, name);
                onRemove(entityMap.remove(name));
            } catch (IOException e) {
                throw new StoreAccessException(e);
            }
            AUDIT.info(type + " " + name + " is removed from config store");
            return true;
        }
        return false;
    }

    private void onRemove(Entity entity) {
        for (ConfigurationChangeListener listener : listeners) {
            try {
                listener.onRemove(entity);
            } catch (Throwable e) {
                LOG.warn(
                        "Encountered exception while notifying " + listener + "(" + entity.getEntityType() + ") " + entity.getName(),
                        e);
            }
        }
    }

    /**
     *
     * @param type
     *            - Entity type that needs to be searched
     * @param keywords
     *            - List of keywords to search for. only entities that have all
     *            the keywords being searched would be returned
     * @return - Array of entity types
     */
    public Entity[] search(EntityType type, String... keywords) {
        return null;// TODO
    }

    /**
     *
     * @param type
     *            - Entity type that is to be stored into persistent storage
     * @param entity
     *            - entity to persist. JAXB Annotated entity will be marshalled
     *            to the persistent store. The convention used for storing the
     *            object:: PROP(config.store.uri)/{entitytype}/{entityname}.xml
     * @throws java.io.IOException
     *             If any error in accessing the storage
     * @throws IvoryException
     */
    private void persist(EntityType type, Entity entity) throws IOException, IvoryException {
        OutputStream out = fs
                .create(new Path(storePath, type + Path.SEPARATOR + URLEncoder.encode(entity.getName(), UTF_8) + ".xml"));
        try {
            type.getMarshaller().marshal(entity, out);
            LOG.info("Persisted configuration " + type + "/" + entity.getName());
        } catch (JAXBException e) {
            LOG.error(e);
            throw new StoreAccessException("Unable to serialize the entity object " + type + "/" + entity.getName(), e);
        } finally {
            out.close();
        }
    }

    /**
     * Archive removed configuration in the persistent store
     *
     * @param type
     *            - Entity type to archive
     * @param name
     *            - name
     * @throws IOException
     *             If any error in accessing the storage
     */
    private void archive(EntityType type, String name) throws IOException {
        Path archivePath = new Path(storePath, "archive" + Path.SEPARATOR + type);
        fs.mkdirs(archivePath);
        fs.rename(new Path(storePath, type + Path.SEPARATOR + URLEncoder.encode(name, UTF_8) + ".xml"), new Path(archivePath,
                URLEncoder.encode(name, UTF_8) + "." + System.currentTimeMillis()));
        LOG.info("Archived configuration " + type + "/" + name);
    }

    /**
     *
     * @param type
     *            - Entity type to restore from persistent store
     * @param name
     *            - Name of the entity to restore.
     * @param <T>
     *            - Actual entity object type
     * @return - De-serialized entity object restored from persistent store
     * @throws IOException
     *             If any error in accessing the storage
     * @throws IvoryException
     */
    @SuppressWarnings("unchecked")
    private synchronized <T extends Entity> T restore(EntityType type, String name) throws IOException, IvoryException {

        InputStream in = fs.open(new Path(storePath, type + Path.SEPARATOR + URLEncoder.encode(name, UTF_8) + ".xml"));
        try {
            return (T) type.getUnmarshaller().unmarshal(in);
        } catch (JAXBException e) {
            throw new StoreAccessException("Unable to un-marshall xml definition for " + type + "/" + name, e);
        } finally {
            in.close();
            LOG.info("Restored configuration " + type + "/" + name);
        }
    }

    public void cleanupUpdateInit() {
        updatesInProgress.set(null);
    }

    @Override
    public String getName() {
        return this.getClass().getName();
    }

    @Override
    public void destroy() { }
}
TOP

Related Classes of org.apache.ivory.entity.store.ConfigurationStore

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.