Package com.asakusafw.yaess.basic

Source Code of com.asakusafw.yaess.basic.FileExecutionLock$LockObject

/**
* Copyright 2011-2014 Asakusa Framework Team.
*
* 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 com.asakusafw.yaess.basic;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileLock;
import java.nio.channels.OverlappingFileLockException;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.asakusafw.yaess.core.ExecutionLock;
import com.asakusafw.yaess.core.YaessLogger;

/**
* An implementation of {@link ExecutionLock} using local file system locks.
* @since 0.2.3
*/
class FileExecutionLock extends ExecutionLock {

    static final YaessLogger YSLOG = new YaessBasicLogger(FileExecutionLock.class);

    static final Logger LOG = LoggerFactory.getLogger(FileExecutionLock.class);

    private static final String NAME_WORLD = "world.lck";

    private static final String NAME_BATCH = "batch-{0}.lck";

    private static final String NAME_FLOW = "flow-{0}-{1}.lck";

    private static final String NAME_EXECUTION = "execution-{0}-{1}-{2}.lck";

    private final Scope lockScope;

    private final String batchId;

    private final File directory;

    private final LockObject batchLock;

    private final Map<String, LockObject> flowLocks;

    private boolean closed;

    /**
     * Creates a new instance.
     * @param lockScope lock scope
     * @param batchId target batch ID
     * @param directory base directory for lock files, must exist
     * @throws IOException if failed to create instance
     * @throws IllegalArgumentException if some parameters were {@code null}
     */
    public FileExecutionLock(Scope lockScope, String batchId, File directory) throws IOException {
        if (lockScope == null) {
            throw new IllegalArgumentException("lockScope must not be null"); //$NON-NLS-1$
        }
        if (batchId == null) {
            throw new IllegalArgumentException("batchId must not be null"); //$NON-NLS-1$
        }
        if (directory == null) {
            throw new IllegalArgumentException("directory must not be null"); //$NON-NLS-1$
        }
        this.lockScope = lockScope;
        this.batchId = batchId;
        this.directory = directory;
        this.flowLocks = new HashMap<String, LockObject>();
        try {
            this.batchLock = acquireForBatch();
        } catch (IOException e) {
            YSLOG.error("E41001", batchId, lockScope);
            throw e;
        }
    }

    @Override
    public synchronized void beginFlow(String flowId, String executionId) throws IOException {
        if (flowId == null) {
            throw new IllegalArgumentException("flowId must not be null"); //$NON-NLS-1$
        }
        if (executionId == null) {
            throw new IllegalArgumentException("executionId must not be null"); //$NON-NLS-1$
        }
        if (closed) {
            throw new IOException("Lock manager is already closed");
        }
        LockObject other = flowLocks.get(flowId);
        if (other != null) {
            YSLOG.error("E41002", batchId, flowId, executionId, lockScope);
            throw new IOException(MessageFormat.format(
                    "Failed to lock target flow (re-entrant): {0} ({1})",
                    flowId,
                    other.label));
        }
        try {
            LockObject lock = acquireForFlow(flowId, executionId);
            if (lock != null) {
                flowLocks.put(flowId, lock);
            }
        } catch (IOException e) {
            YSLOG.error("E41002", batchId, flowId, executionId, lockScope);
            throw e;
        }
    }

    @Override
    public synchronized void endFlow(String flowId, String executionId) throws IOException {
        if (flowId == null) {
            throw new IllegalArgumentException("flowId must not be null"); //$NON-NLS-1$
        }
        if (executionId == null) {
            throw new IllegalArgumentException("executionId must not be null"); //$NON-NLS-1$
        }
        if (closed) {
            throw new IOException("Lock manager is already closed");
        }
        LockObject lock = flowLocks.remove(flowId);
        closeQuiet(lock);
    }

    @Override
    public synchronized void close() {
        if (closed) {
            return;
        }
        closeQuiet(batchLock);
        for (LockObject lock : flowLocks.values()) {
            closeQuiet(lock);
        }
        closed = true;
    }

    private void closeQuiet(LockObject lock) {
        if (lock == null) {
            return;
        }
        lock.close();
    }

    private LockObject acquireForBatch() throws IOException {
        assert directory != null;
        assert batchId != null;
        switch (lockScope) {
        case WORLD:
            return new LockObject(
                    "world lock",
                    new File(directory, NAME_WORLD));
        case BATCH:
            return new LockObject(
                    MessageFormat.format("batch lock - {0}", batchId),
                    new File(directory, MessageFormat.format(NAME_BATCH, batchId)));
        default:
            return null;
        }
    }

    private LockObject acquireForFlow(String flowId, String executionId) throws IOException {
        assert flowId != null;
        assert executionId != null;
        switch (lockScope) {
        case WORLD:
        case BATCH:
        case FLOW:
            return new LockObject(
                    MessageFormat.format("flow lock - {0}/{1}", batchId, flowId),
                    new File(directory, MessageFormat.format(NAME_FLOW, batchId, flowId, executionId)));
        case EXECUTION:
            return new LockObject(
                    MessageFormat.format("execution lock - {2} ({0}/{1})", batchId, flowId, executionId),
                    new File(directory, MessageFormat.format(NAME_EXECUTION, batchId, flowId, executionId)));
        default:
            return null;
        }
    }

    private static class LockObject implements Closeable {

        final String label;

        final File path;

        private final RandomAccessFile file;

        private final FileLock lock;

        private boolean closed;

        LockObject(String label, File path) throws IOException {
            if (path == null) {
                throw new IllegalArgumentException("path must not be null"); //$NON-NLS-1$
            }
            if (label == null) {
                throw new IllegalArgumentException("label must not be null"); //$NON-NLS-1$
            }
            this.label = label;
            this.path = path;
            this.file = new RandomAccessFile(path, "rw");
            boolean succeed = false;
            try {
                this.lock = acquireFileLock();
                succeed = true;
            } finally {
                if (succeed == false) {
                    closeFile();
                }
            }
        }

        private FileLock acquireFileLock() throws IOException {
            assert file != null;
            LOG.debug("Acquiring lock: {}", label);
            FileLock flock;
            try {
                flock = file.getChannel().tryLock();
                if (flock != null) {
                    return flock;
                }
                LOG.debug("Failed to acquire lock: {}", path);
            } catch (OverlappingFileLockException e) {
                // fall through
                LOG.debug(MessageFormat.format(
                        "Lock may be overlapped: {0} ({1})",
                        label,
                        path), e);
            }
            throw new IOException(MessageFormat.format(
                    "Failed to acquire lock: {0}",
                    path));
        }

        @Override
        public void close() {
            if (closed) {
                return;
            }
            closed = true;
            releaseLock();
            closeFile();
            deleteFile();
        }

        private void closeFile() {
            try {
                this.file.close();
            } catch (IOException e) {
                YSLOG.warn(e, "W41002", label, path);
            }
        }

        private void releaseLock() {
            try {
                this.lock.release();
            } catch (IOException e) {
                YSLOG.warn(e, "W41001", label, path);
            }
        }

        private void deleteFile() {
            // FIXME more safe
            if (path.delete() == false) {
                YSLOG.warn("W41003", label, path);
            }
        }
    }
}
TOP

Related Classes of com.asakusafw.yaess.basic.FileExecutionLock$LockObject

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.