Java tutorial
/*************************************************************************** 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(); } }