Package org.lilyproject.repository.model.impl

Source Code of org.lilyproject.repository.model.impl.RepositoryModelImpl

/*
* Copyright 2013 NGDATA nv
*
* Licensed 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.lilyproject.repository.model.impl;

import com.google.common.collect.Sets;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.data.Stat;
import org.lilyproject.repository.model.api.RepositoryDefinition;
import org.lilyproject.repository.model.api.RepositoryExistsException;
import org.lilyproject.repository.model.api.RepositoryModel;
import org.lilyproject.repository.model.api.RepositoryModelEvent;
import org.lilyproject.repository.model.api.RepositoryModelEventType;
import org.lilyproject.repository.model.api.RepositoryModelException;
import org.lilyproject.repository.model.api.RepositoryModelListener;
import org.lilyproject.repository.model.api.RepositoryNotFoundException;
import org.lilyproject.util.zookeeper.ZkUtil;
import org.lilyproject.util.zookeeper.ZooKeeperItf;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static org.apache.zookeeper.Watcher.Event.EventType.NodeChildrenChanged;
import static org.apache.zookeeper.Watcher.Event.EventType.NodeDataChanged;
import static org.lilyproject.repository.model.api.RepositoryDefinition.RepositoryLifecycleState;

public class RepositoryModelImpl implements RepositoryModel {
    private Map<String, RepositoryDefinition> repos = new HashMap<String, RepositoryDefinition>();

    /**
     * Lock to be obtained when changing repos or when dispatching events. This assures the correct functioning
     * of methods like {@link #getRepositories(org.lilyproject.repository.model.api.RepositoryModelListener)}.
     */
    private final Object reposLock = new Object();

    private ZooKeeperItf zk;

    private final Set<RepositoryModelListener> listeners =
            Collections.newSetFromMap(new IdentityHashMap<RepositoryModelListener, Boolean>());

    private Watcher zkWatcher;

    private boolean closed = false;

    private static final String REPOSITORY_COLLECTION_PATH = "/lily/repositorymodel/repositories";

    private static final String REPOSITORY_COLLECTION_PATH_SLASH = REPOSITORY_COLLECTION_PATH + "/";

    private final Log log = LogFactory.getLog(getClass());

    /**
     * The default repository always exists and is always in state active.
     */
    private static final RepositoryDefinition DEFAULT_REPOSITORY = new RepositoryDefinition("default",
            RepositoryLifecycleState.ACTIVE);

    public RepositoryModelImpl(ZooKeeperItf zk) throws KeeperException, InterruptedException {
        this.zk = zk;
        init();
    }

    public void init() throws KeeperException, InterruptedException {
        ZkUtil.createPath(zk, REPOSITORY_COLLECTION_PATH);
        assureDefaultRepositoryExists();
        zkWatcher = new RepositoryZkWatcher();
        zk.addDefaultWatcher(zkWatcher);
        refresh();
    }

    private void assureDefaultRepositoryExists() throws KeeperException, InterruptedException {
        byte[] repoBytes = RepositoryDefinitionJsonSerDeser.INSTANCE.toJsonBytes(DEFAULT_REPOSITORY);
        try {
            zk.create(REPOSITORY_COLLECTION_PATH + "/" + DEFAULT_REPOSITORY.getName(), repoBytes, ZooDefs.Ids.OPEN_ACL_UNSAFE,
                    CreateMode.PERSISTENT);
        } catch (KeeperException.NodeExistsException e) {
            // it already exists, fine
        }
    }

    public void close() {
        zk.removeDefaultWatcher(zkWatcher);
        closed = true;
    }

    @Override
    public void create(String repositoryName) throws RepositoryExistsException, InterruptedException, RepositoryModelException {
        if (!RepoDefUtil.isValidRepositoryName(repositoryName)) {
            throw new IllegalArgumentException(String.format("'%s' is not a valid repository name. "
                    + RepoDefUtil.VALID_NAME_EXPLANATION, repositoryName));
        }
        RepositoryDefinition repoDef = new RepositoryDefinition(repositoryName, RepositoryLifecycleState.CREATE_REQUESTED);
        byte[] repoBytes = RepositoryDefinitionJsonSerDeser.INSTANCE.toJsonBytes(repoDef);
        try {
            zk.create(REPOSITORY_COLLECTION_PATH + "/" + repositoryName, repoBytes, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        } catch (KeeperException.NodeExistsException e) {
            throw new RepositoryExistsException("Can't create repository, a repository with this name already exists: "
                    + repositoryName);
        } catch (KeeperException e) {
            throw new RepositoryModelException(e);
        }
    }

    private void disallowDefaultRepository(String repositoryName) throws RepositoryModelException {
        if (DEFAULT_REPOSITORY.getName().equals(repositoryName)) {
            throw new RepositoryModelException("This operation is not allowed on the default repository.");
        }
    }

    @Override
    public void delete(String repositoryName)
            throws InterruptedException, RepositoryModelException, RepositoryNotFoundException {
        disallowDefaultRepository(repositoryName);
        RepositoryDefinition repoDef = new RepositoryDefinition(repositoryName, RepositoryLifecycleState.DELETE_REQUESTED);
        try {
            storeRepository(repoDef);
        } catch (KeeperException.NoNodeException e) {
            throw new RepositoryNotFoundException("Can't delete-request repository, a repository with this name doesn't exist: "
                    + repoDef.getName());
        } catch (KeeperException e) {
            throw new RepositoryModelException(e);
        }
    }

    @Override
    public void deleteDirect(String repositoryName)
            throws InterruptedException, RepositoryModelException, RepositoryNotFoundException {
        disallowDefaultRepository(repositoryName);
        try {
            zk.delete(REPOSITORY_COLLECTION_PATH + "/" + repositoryName, -1);
        } catch (KeeperException.NoNodeException e) {
            throw new RepositoryNotFoundException("Can't delete repository, a repository with this name doesn't exist: "
                    + repositoryName);
        } catch (KeeperException e) {
            throw new RepositoryModelException("Error deleting repository.", e);
        }
    }

    @Override
    public void updateRepository(RepositoryDefinition repoDef)
            throws InterruptedException, RepositoryModelException,RepositoryNotFoundException {
        disallowDefaultRepository(repoDef.getName());
        try {
            storeRepository(repoDef);
        } catch (KeeperException.NoNodeException e) {
            throw new RepositoryNotFoundException("Can't update repository, a repository with this name doesn't exist: "
                    + repoDef.getName());
        } catch (KeeperException e) {
            throw new RepositoryModelException(e);
        }
    }

    public void storeRepository(RepositoryDefinition repoDef) throws InterruptedException, KeeperException {
        byte[] repoBytes = RepositoryDefinitionJsonSerDeser.INSTANCE.toJsonBytes(repoDef);
        zk.setData(REPOSITORY_COLLECTION_PATH + "/" + repoDef.getName(), repoBytes, -1);
    }

    @Override
    public Set<RepositoryDefinition> getRepositories() throws RepositoryModelException, InterruptedException {
        try {
            return new HashSet<RepositoryDefinition>(loadRepositories(false).values());
        } catch (KeeperException e) {
            throw new RepositoryModelException("Error loading repositories.", e);
        }
    }

    @Override
    public RepositoryDefinition getRepository(String repositoryName)
            throws InterruptedException, RepositoryModelException, RepositoryNotFoundException {
        try {
            return loadRepository(repositoryName, false);
        } catch (KeeperException.NoNodeException e) {
            throw new RepositoryNotFoundException("No repository named " + repositoryName, e);
        } catch (KeeperException e) {
            throw new RepositoryModelException("Error loading repository " + repositoryName, e);
        }
    }

    @Override
    public boolean repositoryExistsAndActive(String repositoryName) {
        RepositoryDefinition repoDef = repos.get(repositoryName);
        return repoDef != null && repoDef.getLifecycleState() == RepositoryLifecycleState.ACTIVE;
    }

    @Override
    public boolean repositoryActive(String repositoryName) throws RepositoryNotFoundException {
        RepositoryDefinition repoDef = repos.get(repositoryName);
        if (repoDef == null) {
            throw new RepositoryNotFoundException("No repository named " + repositoryName);
        }
        return repoDef.getLifecycleState() == RepositoryLifecycleState.ACTIVE;
    }

    @Override
    public boolean waitUntilRepositoryInState(String repositoryName, RepositoryLifecycleState state, long timeout)
            throws InterruptedException {
        long waitUntil = System.currentTimeMillis() + timeout;
        while (!repositoryExistsAndActive(repositoryName) && System.currentTimeMillis() < waitUntil) {
            Thread.sleep(50);
        }
        return repositoryExistsAndActive(repositoryName);
    }

    private class RepositoryZkWatcher implements Watcher {
        @Override
        public void process(WatchedEvent event) {
            boolean needsRefresh = false;
            if (event.getType() == Event.EventType.None && event.getState() == Event.KeeperState.SyncConnected) {
                needsRefresh = true;
            } else if (NodeChildrenChanged.equals(event.getType()) && event.getPath().equals(REPOSITORY_COLLECTION_PATH)) {
                needsRefresh = true;
            } else if (NodeDataChanged.equals(event.getType()) && event.getPath().startsWith(REPOSITORY_COLLECTION_PATH_SLASH)) {
                needsRefresh = true;
            }

            if (needsRefresh) {
                try {
                    refresh();
                } catch (Throwable t) {
                    log.error("Repository Model: error handling event from ZooKeeper. Event: " + event, t);
                }
            }
        }
    }

    private void refresh() throws KeeperException, InterruptedException {
        List<RepositoryModelEvent> events = new ArrayList<RepositoryModelEvent>();

        synchronized (reposLock) {
            Map<String, RepositoryDefinition> newRepos = loadRepositories(true);

            // Find out changes in repositories
            Set<String> removedRepos = Sets.difference(repos.keySet(), newRepos.keySet());
            for (String id : removedRepos) {
                events.add(new RepositoryModelEvent(RepositoryModelEventType.REPOSITORY_REMOVED, id));
            }

            Set<String> addedRepos = Sets.difference(newRepos.keySet(), repos.keySet());
            for (String id : addedRepos) {
                events.add(new RepositoryModelEvent(RepositoryModelEventType.REPOSITORY_ADDED, id));
            }

            for (RepositoryDefinition repoDef : newRepos.values()) {
                if (repos.containsKey(repoDef.getName()) && !repos.get(repoDef.getName()).equals(repoDef)) {
                    events.add(new RepositoryModelEvent(RepositoryModelEventType.REPOSITORY_UPDATED, repoDef.getName()));
                }
            }

            repos = newRepos;
        }

        notifyListeners(events);
    }

    private Map<String, RepositoryDefinition> loadRepositories(boolean watch) throws KeeperException, InterruptedException {
        Map<String, RepositoryDefinition> repositories = new HashMap<String, RepositoryDefinition>();
        List<String> children = zk.getChildren(REPOSITORY_COLLECTION_PATH, watch ? zkWatcher : null);
        for (String child : children) {
            repositories.put(child, loadRepository(child, watch));
        }
        return repositories;
    }

    private RepositoryDefinition loadRepository(String name, boolean watch) throws KeeperException, InterruptedException {
        byte[] repoJson = zk.getData(REPOSITORY_COLLECTION_PATH + "/" + name, watch ? zkWatcher : null, new Stat());
        return RepositoryDefinitionJsonSerDeser.INSTANCE.fromJsonBytes(name, repoJson);
    }

    private void notifyListeners(List<RepositoryModelEvent> events) {
        if (closed) {
            // Stop dispatching events once closed
            return;
        }

        for (RepositoryModelEvent event : events) {
            for (RepositoryModelListener listener : listeners) {
                listener.process(event);
            }
        }
    }

    @Override
    public Set<RepositoryDefinition> getRepositories(RepositoryModelListener listener) {
        synchronized (reposLock) {
            registerListener(listener);
            return new HashSet<RepositoryDefinition>(repos.values());
        }
    }

    @Override
    public void registerListener(RepositoryModelListener listener) {
        synchronized (reposLock) {
            listeners.add(listener);
        }
    }

    @Override
    public void unregisterListener(RepositoryModelListener listener) {
        listeners.remove(listener);
    }
}
TOP

Related Classes of org.lilyproject.repository.model.impl.RepositoryModelImpl

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.