net.metanotion.exportimport.BasicInstanceExport.java Source code

Java tutorial

Introduction

Here is the source code for net.metanotion.exportimport.BasicInstanceExport.java

Source

/***************************************************************************
   Copyright 2015 Emily Estes
    
   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 net.metanotion.exportimport;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicReference;

import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

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

import net.metanotion.contentstore.Collection;
import net.metanotion.contentstore.ContentStore;
import net.metanotion.contentstore.Header;
import net.metanotion.contentstore.StoreUtils;
import net.metanotion.web.HttpValues;
import net.metanotion.web.concrete.HttpUtil;

/** <p>This class is a reasonable default implementation of {@link InstanceExport}. It does require your web application
instances to incorporate an {@link net.metanotion.contentstore.ContentStore} to provide storage management for the
export files produced, if this is not acceptable, you may need to consider writing your own implementation. This
implementation also relies on your application to provide an implementation of the {@link ExportImport} interface to
delegate the actual work of exporting and importing files into an instance.</p> */
public final class BasicInstanceExport implements InstanceExport {
    private static final Logger logger = LoggerFactory.getLogger(BasicInstanceExport.class);

    private static final String INVALID_EXPORT_ID = "Invalid export id.";

    private static final class ConcurrentBoundedBuffer<V> {
        private final V[] array;

        private int start = 0;
        private int end = 0;

        public ConcurrentBoundedBuffer(final int size) {
            this.array = (V[]) new Object[size];
        }

        public synchronized void clear() {
            this.start = 0;
            this.end = 0;
        }

        public synchronized void add(final V value) {
            this.array[this.end] = value;
            this.end = (end + 1) % this.array.length;
            if (this.end == this.start) {
                this.start = (start + 1) % this.array.length;
            }
        }

        public synchronized List<V> list() {
            final ArrayList<V> ret = new ArrayList<>();
            if (this.start == this.end) {
                return ret;
            } else if (start < end) {
                for (int i = start; i < end; i++) {
                    ret.add(this.array[i]);
                }
            } else {
                for (int i = start; i < this.array.length; i++) {
                    ret.add(this.array[i]);
                }
                for (int i = 0; i < end; i++) {
                    ret.add(this.array[i]);
                }
            }
            return ret;
        }
    }

    private final ContentStore store;
    private final String backupCollection;
    private final ExecutorService queue;
    private final ExportImport instance;
    private final AtomicReference<ConcurrentBoundedBuffer<String>> currentJob = new AtomicReference<>();

    /** Create a new instance of the InstanceExport service.
       @param store The content store implementation providing durable storage for export files.
       @param backupCollection The name of the collection to use in the content stare for file storage.
       @param queue The executor service to submit export, import, and reset instance jobs to. Calls to the
     {@link ExportImport} service will be submitted to this service to allow for non-blocking behavior under the
     assumption that these operations might block interactive responses made to this implementation.
       @param instance The application specific implementation of the {@link ExportImport} service to delegate the
     actual creation and use of export files to and to resent the application instances.
    */
    public BasicInstanceExport(final ContentStore store, final String backupCollection, final ExecutorService queue,
            final ExportImport instance) {
        this.store = store;
        this.backupCollection = backupCollection;
        this.queue = queue;
        this.instance = instance;
    }

    @Override
    public Status status() {
        final ConcurrentBoundedBuffer<String> job = currentJob.get();
        logger.debug("Job status {}", job);
        if (job == null) {
            final List<String> e = Collections.emptyList();
            return new Status(false, e);
        } else {
            final List<String> e = job.list();
            logger.debug("List: {}", e);
            return new Status(true, e);
        }
    }

    @Override
    public Status initiateExport() {
        final ConcurrentBoundedBuffer<String> messageList = new ConcurrentBoundedBuffer<>(10);
        messageList.add("msg_exportStarted");
        if (currentJob.compareAndSet(null, messageList)) {
            queue.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        final Collection media = store.getCollection(backupCollection);
                        final String filename = instance.getBackupFilename();
                        final String mimeType = instance.getBackupMimeType();
                        final File temp = File.createTempFile("BasicInstanceExport.", ".backup");
                        try (final OutputStream out = new FileOutputStream(temp)) {
                            instance.exportTo(out);
                        }
                        final net.metanotion.contentstore.Entry e = media.append(filename);
                        try (final InputStream in = new FileInputStream(temp)) {
                            e.update(in, filename, mimeType);
                        }
                        temp.delete();
                        messageList.add("msg_exportComplete");
                    } catch (final RuntimeException | IOException ex) {
                        logger.error("{}", ex);
                    }
                    currentJob.set(null);
                }
            });
        }
        return status();
    }

    @Override
    public Status resetInstance() {
        final ConcurrentBoundedBuffer<String> messageList = new ConcurrentBoundedBuffer<>(10);
        messageList.add("msg_resetStarted");
        if (currentJob.compareAndSet(null, messageList)) {
            queue.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        instance.resetInstance();
                        messageList.add("msg_resetComplete");
                    } catch (final Exception ex) {
                        logger.error("{}", ex);
                    }
                    currentJob.set(null);
                }
            });
        }
        return status();
    }

    private static String getStoreTime() {
        final DateTimeFormatter dateFormat = DateTimeFormat.forPattern("YYYY.MM.dd-HH.mm");
        return dateFormat.print(new DateTime());
    }

    @Override
    public void storeExternalExport(final String filename, final String mimeType, final InputStream in) {
        final Collection media = store.getCollection(backupCollection);
        final net.metanotion.contentstore.Entry e = media
                .append("File uploaded '" + getStoreTime() + "' - " + filename);
        e.update(in, filename, mimeType);
    }

    @Override
    public List<InstanceExport.Entry> listExports(int offset, final int count) {
        final ArrayList<InstanceExport.Entry> list = new ArrayList<>();
        final Collection media = store.getCollection(backupCollection);
        for (final Header h : media.header(count, offset)) {
            final InstanceExport.Entry e = new InstanceExport.Entry();
            e.description = h.Title;
            e.fileId = h.id;
            e.offset = offset;
            offset++;
            list.add(e);
        }
        return list;
    }

    @Override
    public void deleteExport(final long fileId) {
        final net.metanotion.contentstore.Entry e = store.getEntry(fileId);
        if (e.getCollection().oid() != store.getCollection(backupCollection).oid()) {
            throw new RuntimeException(INVALID_EXPORT_ID);
        }
        e.delete();
    }

    @Override
    public HttpValues getExport(final long fileId) {
        final net.metanotion.contentstore.Entry e = store.getEntry(fileId);
        if (e.getCollection().oid() != store.getCollection(backupCollection).oid()) {
            return HttpUtil.new404("Invalid Export");
        }
        return StoreUtils.get(e);
    }

    @Override
    public Status initiateImport(final long fileId) {
        final ConcurrentBoundedBuffer<String> messageList = new ConcurrentBoundedBuffer<>(10);
        messageList.add("msg_importStarted");
        if (currentJob.compareAndSet(null, messageList)) {
            final net.metanotion.contentstore.Entry e = store.getEntry(fileId);
            if (e.getCollection().oid() != store.getCollection(backupCollection).oid()) {
                throw new RuntimeException(INVALID_EXPORT_ID);
            }
            queue.submit(new Runnable() {
                @Override
                public void run() {
                    try (final InputStream in = e.readFile()) {
                        instance.importFrom(in);
                        messageList.add("msg_importComplete");
                    } catch (final Exception ex) {
                        logger.error("{}", ex);
                    }
                    currentJob.set(null);
                }
            });
        }
        return status();
    }
}