Package org.apache.ace.log.target.store.impl

Source Code of org.apache.ace.log.target.store.impl.LogStoreImpl$Store

/*
* 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.ace.log.target.store.impl;

import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.ace.feedback.Event;
import org.apache.ace.identification.Identification;
import org.apache.ace.log.target.store.LogStore;
import org.osgi.service.log.LogService;

/**
* This class provides an implementation of the LogStore service. It tries to
* repair broken log files to make them readable again. However, this might lead
* to loss of data. Additionally, a new file is used when an error is detected.
*/
public class LogStoreImpl implements LogStore {
    // injected by dependencymanager
    volatile Identification m_identification;
    volatile LogService m_log;

    // The current store
    private Store m_store = null;
    private final File m_baseDir;
    private long m_highest;

    /**
     * Create new instance using the specified directory as root directory.
     *
     * @param baseDir
     *            Root directory to use for storage.
     */
    public LogStoreImpl(File baseDir) {
        m_baseDir = new File(baseDir, "store");
    }

    /**
     * Init the current store.
     *
     * @throws java.io.IOException
     */
    protected synchronized void start() throws IOException {
        if (!m_baseDir.isDirectory() && !m_baseDir.mkdirs()) {
            throw new IllegalArgumentException("Need valid dir");
        }
        long current = -1;
        File[] files = (File[]) notNull(m_baseDir.listFiles());
        for (int i = 0; i < files.length; i++) {
            long id = Long.parseLong(files[i].getName());
            current = Math.max(id, current);
        }
        try {
            if (current == -1) {
                m_store = newStore();
            } else {
                m_store = createStore(current);
                try {
                    m_store.init();
                }
                catch (IOException ex) {
                    handleException(m_store, ex);
                }
            }
        }
        catch (IOException ex) {
            // We should be able to recover from the error.
            m_log.log(LogService.LOG_ERROR, "Exception during log store init",
                    ex);
        }
    }

    /**
     * Close the current store.
     *
     * @throws java.io.IOException
     *             in case of any IO error.
     */
    protected synchronized void stop() throws IOException {
        m_store.close();
        m_store = null;
    }

    /**
     * Create a store object for a new log.
     *
     * @return a store object for a new log.
     * @throws java.io.IOException
     *             in case of any IO error.
     */
    protected Store newStore() throws IOException {
        long id = System.currentTimeMillis();

        while (!(new File(m_baseDir, String.valueOf(id))).createNewFile()) {
            id++;
        }

        return new Store(new File(m_baseDir, String.valueOf(id)), id);
    }

    /**
     * Create a store object for the given log. This should not be used to
     * create a new log.
     *
     * @param id
     *            the id of the log.
     * @return a new store object for the given log.
     * @throws java.io.IOException
     *             in case of an IO error.
     */
    protected Store createStore(long id) throws IOException {
        return new Store(new File(m_baseDir, String.valueOf(id)), id);
    }

    /**
     * Get the entries in the given range from the given log.
     *
     * @param logID
     *            the id of the log.
     * @param from
     *            the lower bound of the range.
     * @param to
     *            the upper bound of the range.
     * @return a list of entries from the given log in the given range.
     * @throws java.io.IOException
     *             in case of any IO error.
     */
    public synchronized List get(long logID, long from, long to)
            throws IOException {
        Store store = getLog(logID);
        List result = new ArrayList();
        try {
            if (store.getCurrent() > from) {
                store.reset();
            }

            while (store.hasNext()) {
                long eventID = store.readCurrentID();
                if ((eventID >= from) && (eventID <= to)) {
                    result.add(new Event(new String(store.read())));
                } else {
                    store.skip();
                }
            }
        }
        catch (Exception ex) {
            handleException(store, ex);
        }
        finally {
            closeIfNeeded(store);
        }
        return result;
    }

    /**
     * Try to repair the given store, log the given exception and rethrow it. In
     * case the store is the current log switch to a new one if possible.
     *
     * @param store
     *            the store to repair/close.
     * @param exception
     *            the exception to log and rethrow.
     * @throws java.io.IOException
     *             the given exception if it is an IOException else the message
     *             of the given exception wrapped in an IOException.
     */
    protected void handleException(Store store, Exception exception)
            throws IOException {
        m_log.log(LogService.LOG_WARNING, "Exception accessing the log: "
                + store.getId(), exception);
        if (store == m_store) {
            m_store = newStore();
        }

        try {
            store.truncate();
        }
        catch (IOException ex) {
            m_log.log(LogService.LOG_WARNING, "Exception during truncate: "
                    + store.getId(), ex);
        }
        try {
            store.close();
        }
        catch (IOException ex) {
            // Not much we can do
        }
        if (exception instanceof IOException) {
            throw (IOException) exception;
        }
        throw new IOException("Unable to read log entry: "
                + exception.getMessage());
    }

    /**
     * Get all entries of the given log.
     *
     * @param logID
     *            the id of the log.
     * @return a list of all entries in this log.
     * @throws java.io.IOException
     *             in case of any IO error.
     */
    public List get(long logID) throws IOException {
        return get(logID, 0, Long.MAX_VALUE);
    }

    /**
     * Get the current log ids.
     *
     * @return the ids of the current logs.
     */
    public long[] getLogIDs() throws IOException {
        File[] files = (File[]) notNull(m_baseDir.listFiles());
        long[] result = new long[files.length];
        for (int i = 0; i < files.length; i++) {
            result[i] = Long.parseLong(files[i].getName());
        }
        return result;
    }

    /**
     * Create and add a LogEvent to the current log.
     *
     * @param type
     *            the type the event.
     * @param dict
     *            the properties of the event.
     * @return the new event.
     * @throws java.io.IOException
     *             in case of any IO error.
     */
    public synchronized Event put(int type, Dictionary dict) throws IOException {
        try {
            Map<String, String> props = new HashMap<String, String>();
            Enumeration keys = dict.keys();
            while (keys.hasMoreElements()) {
                String key = (String) keys.nextElement();
                props.put(key, (String) dict.get(key));
            }
           
            Event result = new Event(null, m_store.getId(), getNextID(), System.currentTimeMillis(), type, props);
            m_store.append(result.getID(), result.toRepresentation().getBytes());
            return result;
        }
        catch (IOException ex) {
            handleException(m_store, ex);
        }
        return null;
    }

    /**
     * Get the highest entry id of the given log.
     *
     * @param logID
     *            the id of the log.
     * @return the id of the highest entry.
     * @throws java.io.IOException
     *             in case of any IO error.
     */
    public synchronized long getHighestID(long logID) throws IOException {
        Store store = getLog(logID);
        try {
            if (m_highest == 0) {
                store.init();
                return (m_highest = store.getCurrent());
            } else {
                return m_highest;
            }
        }
        catch (IOException ex) {
            handleException(store, ex);
        }
        finally {
            closeIfNeeded(store);
        }
        return -1;
    }

    /**
     * Close the given store if it is not the current store. IO errors are
     * ignored.
     *
     * @param store
     *            the store to close.
     */
    protected void closeIfNeeded(Store store) {
        if (store != m_store) {
            try {
                store.close();
            }
            catch (IOException ex) {
                // Not much we can do;
            }
        }
    }

    /**
     * Get a Store object for the log of the given logid.
     *
     * @param logID
     *            the id for which to return (and possibly create) a store.
     * @return either a new or the current Store object.
     * @throws java.io.IOException
     *             in case of any IO error.
     */
    protected Store getLog(long logID) throws IOException {
        if (m_store.getId() == logID) {
            return m_store;
        }
        return createStore(logID);
    }

    /**
     * Get the next id for the current store.
     *
     * @return the next free log id of the current store.
     * @throws java.io.IOException
     */
    protected long getNextID() throws IOException {
        return (m_highest = getHighestID(m_store.m_id) + 1);
    }

    /*
     * throw IOException in case the target is null else return the target.
     */
    private Object notNull(Object target) throws IOException {
        if (target == null) {
            throw new IOException(
                    "Unknown IO error while trying to access the store.");
        }
        return target;
    }

    /**
     * The general idea is to provide easy access to a file of records. It
     * supports iterating over records both by skipping and by reading.
     * Furthermore, files can be truncated. Most methods will make an effort to
     * reset to the last good record in case of an error -- hence, a call to
     * truncate after an IOException might make the store readable again.
     */
    class Store {
        private final RandomAccessFile m_store;
        private final long m_id;
        private long m_current;

        /**
         * Create a new File based Store.
         *
         * @param store
         *            the file to use as backend.
         * @param id
         *            the log id of the store
         * @throws java.io.IOException
         *             in case the file is not rw.
         */
        Store(File store, long id) throws IOException {
            m_store = new RandomAccessFile(store, "rwd");
            m_id = id;
        }

        /**
         * Get the id of the current record.
         *
         * @return the idea of the current record.
         */
        public long getCurrent() throws IOException {
            long pos = m_store.getFilePointer();
            if (m_store.length() == 0) {
                return 0;
            }
            long result = 0;
            try {
                m_store.seek(m_current);
                result = readCurrentID();
                m_store.seek(pos);
            }
            catch (IOException ex) {
                handle(pos, ex);
            }
            return result;
        }

        /**
         * Get the log id of this store.
         *
         * @return the log id of this store.
         */
        public long getId() {
            return m_id;
        }

        /**
         * Reset the store to the beginning of the records
         *
         * @throws java.io.IOException
         *             in case of an IO error.
         */
        public void reset() throws IOException {
            m_store.seek(0);
            m_current = 0;
        }

        /**
         * Determine whether there are any records left based on the current
         * postion.
         *
         * @return <code>true</code> if there are still records to be read.
         * @throws java.io.IOException
         *             in case of an IO error.
         */
        public boolean hasNext() throws IOException {
            return m_store.getFilePointer() < m_store.length();
        }

        public byte[] read() throws IOException {
            long pos = m_store.getFilePointer();
            try {
                if (pos < m_store.length()) {
                    long current = m_store.getFilePointer();
                    long id = m_store.readLong();
                    int next = m_store.readInt();
                    byte[] entry = new byte[next];
                    m_store.readFully(entry);
                    m_current = current;
                    return entry;
                }
            }
            catch (IOException ex) {
                handle(pos, ex);
            }
            return null;
        }

        public long readCurrentID() throws IOException {
            long pos = m_store.getFilePointer();
            try {
                if (pos < m_store.length()) {
                    long id = m_store.readLong();
                    m_store.seek(pos);
                    return id;
                }
            }
            catch (IOException ex) {
                handle(pos, ex);
            }
            return -1;
        }

        /**
         * Make sure the store is readable. As a result, the store is at the end
         * of the records.
         *
         * @throws java.io.IOException
         *             in case of any IO error.
         */
        public void init() throws IOException {
            reset();
            try {
                while (true) {
                    skip();
                }
            }
            catch (EOFException ex) {
                // done
            }
        }

        /**
         * Skip the next record if there is any.
         *
         * @throws java.io.IOException
         *             in case of any IO error or if there is no record left.
         */
        public void skip() throws IOException {
            long pos = m_store.getFilePointer();
            try {
                long id = m_store.readLong();
                int next = m_store.readInt();
                if (m_store.length() < next + m_store.getFilePointer()) {
                    throw new IOException("Unexpected end of file");
                }
                m_store.seek(m_store.getFilePointer() + next);
                m_current = pos;
                pos = m_store.getFilePointer();
            }
            catch (IOException ex) {
                handle(pos, ex);
            }
        }

        /**
         * Store the given record data as the next record.
         *
         * @param entry
         *            the data of the record to store.
         * @throws java.io.IOException
         *             in case of any IO error.
         */
        public void append(long id, byte[] entry) throws IOException {
            long pos = m_store.getFilePointer();
            try {
                m_store.seek(m_store.length());
                long current = m_store.getFilePointer();
                m_store.writeLong(id);
                m_store.writeInt(entry.length);
                m_store.write(entry);
                m_store.seek(pos);
            }
            catch (IOException ex) {
                handle(pos, ex);
            }
        }

        /**
         * Try to truncate the store at the current record.
         *
         * @throws java.io.IOException
         *             in case of any IO error.
         */
        public void truncate() throws IOException {
            m_store.setLength(m_store.getFilePointer());
        }

        /**
         * Release any resources.
         *
         * @throws java.io.IOException
         *             in case of any IO error.
         */
        public void close() throws IOException {
            m_store.close();
        }

        private void handle(long pos, IOException exception) throws IOException {
            try {
                m_store.seek(pos);
            }
            catch (IOException ex) {
                m_log.log(LogService.LOG_WARNING, "Exception during seek!", ex);
            }
            throw exception;
        }
    }
}
TOP

Related Classes of org.apache.ace.log.target.store.impl.LogStoreImpl$Store

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.