/* (c) 2016 Open Source Geospatial Foundation - all rights reserved * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.backuprestore.reader; import java.io.IOException; import java.util.List; import org.geoserver.backuprestore.Backup; import org.geoserver.backuprestore.BackupRestoreItem; import org.geoserver.catalog.Catalog; import org.geoserver.config.util.XStreamPersister; import org.geoserver.config.util.XStreamPersisterFactory; import org.geoserver.platform.GeoServerExtensions; import org.geoserver.platform.resource.Files; import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.item.ItemCountAware; import org.springframework.batch.item.ItemReader; import org.springframework.batch.item.ItemStream; import org.springframework.batch.item.ItemStreamException; import org.springframework.batch.item.ItemStreamReader; import org.springframework.batch.item.ParseException; import org.springframework.batch.item.UnexpectedInputException; import org.springframework.batch.item.file.ResourceAwareItemReaderItemStream; import org.springframework.batch.item.util.ExecutionContextUserSupport; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.io.Resource; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; /** * Abstract Spring Batch {@link ItemReader}. * * Configures the {@link Catalog} and initizializes the {@link XStreamPersister}. * * @author Alessio Fabiani, GeoSolutions * */ @SuppressWarnings({ "rawtypes" }) public abstract class CatalogReader<T> extends BackupRestoreItem<T> implements ItemStream, ItemStreamReader<T>, ResourceAwareItemReaderItemStream<T>, InitializingBean { protected Class clazz; public CatalogReader(Class<T> clazz, Backup backupFacade, XStreamPersisterFactory xStreamPersisterFactory) { super(backupFacade, xStreamPersisterFactory); this.clazz = clazz; this.setExecutionContextName(ClassUtils.getShortName(clazz)); } private static final String READ_COUNT = "read.count"; private static final String READ_COUNT_MAX = "read.count.max"; private int currentItemCount = 0; private int maxItemCount = Integer.MAX_VALUE; private boolean saveState = true; private final ExecutionContextUserSupport executionContextUserSupport = new ExecutionContextUserSupport(); /** * The name of the component which will be used as a stem for keys in the {@link ExecutionContext}. Subclasses should provide a default value, * e.g. the short form of the class name. * * @param name the name for the component */ public void setName(String name) { this.setExecutionContextName(name); } protected void setExecutionContextName(String name) { executionContextUserSupport.setName(name); } public String getExecutionContextKey(String key) { return executionContextUserSupport.getKey(key); } /** * Read next item from input. * * @return item * @throws Exception Allows subclasses to throw checked exceptions for interpretation by the framework */ protected abstract T doRead() throws Exception; /** * Open resources necessary to start reading input. * * @throws Exception Allows subclasses to throw checked exceptions for interpretation by the framework */ protected abstract void doOpen() throws Exception; /** * Close the resources opened in {@link #doOpen()}. * * @throws Exception Allows subclasses to throw checked exceptions for interpretation by the framework */ protected abstract void doClose() throws Exception; /** * Move to the given item index. Subclasses should override this method if there is a more efficient way of moving to given index than re-reading * the input using {@link #doRead()}. * * @param itemIndex index of item (0 based) to jump to. * @throws Exception Allows subclasses to throw checked exceptions for interpretation by the framework */ protected void jumpToItem(int itemIndex) throws Exception { for (int i = 0; i < itemIndex; i++) { read(); } } @Override public T read() throws Exception, UnexpectedInputException, ParseException { if (currentItemCount >= maxItemCount) { return null; } currentItemCount++; T item = doRead(); if (item instanceof ItemCountAware) { ((ItemCountAware) item).setItemCount(currentItemCount); } return item; } protected int getCurrentItemCount() { return currentItemCount; } /** * The index of the item to start reading from. If the {@link ExecutionContext} contains a key <code>[name].read.count</code> (where * <code>[name]</code> is the name of this component) the value from the {@link ExecutionContext} will be used in preference. * * @see #setName(String) * * @param count the value of the current item count */ public void setCurrentItemCount(int count) { this.currentItemCount = count; } /** * The maximum index of the items to be read. If the {@link ExecutionContext} contains a key <code>[name].read.count.max</code> (where * <code>[name]</code> is the name of this component) the value from the {@link ExecutionContext} will be used in preference. * * @see #setName(String) * * @param count the value of the maximum item count */ public void setMaxItemCount(int count) { this.maxItemCount = count; } @Override public void close() throws ItemStreamException { currentItemCount = 0; try { doClose(); } catch (Exception e) { throw new ItemStreamException("Error while closing item reader", e); } } @Override public void open(ExecutionContext executionContext) throws ItemStreamException { try { doOpen(); } catch (Exception e) { throw new ItemStreamException("Failed to initialize the reader", e); } if (!isSaveState()) { return; } if (executionContext.containsKey(getExecutionContextKey(READ_COUNT_MAX))) { maxItemCount = executionContext.getInt(getExecutionContextKey(READ_COUNT_MAX)); } int itemCount = 0; if (executionContext.containsKey(getExecutionContextKey(READ_COUNT))) { itemCount = executionContext.getInt(getExecutionContextKey(READ_COUNT)); } else if (currentItemCount > 0) { itemCount = currentItemCount; } if (itemCount > 0 && itemCount < maxItemCount) { try { jumpToItem(itemCount); } catch (Exception e) { throw new ItemStreamException("Could not move to stored position on restart", e); } } currentItemCount = itemCount; } @Override public void update(ExecutionContext executionContext) throws ItemStreamException { if (saveState) { Assert.notNull(executionContext, "ExecutionContext must not be null"); executionContext.putInt(getExecutionContextKey(READ_COUNT), currentItemCount); if (maxItemCount < Integer.MAX_VALUE) { executionContext.putInt(getExecutionContextKey(READ_COUNT_MAX), maxItemCount); } } } @SuppressWarnings({ "unchecked" }) protected void firePostRead(T item, Resource resource) throws IOException { List<CatalogAdditionalResourcesReader> additionalResourceReaders = GeoServerExtensions .extensions(CatalogAdditionalResourcesReader.class); for (CatalogAdditionalResourcesReader rd : additionalResourceReaders) { if (rd.canHandle(item)) { rd.readAdditionalResources(backupFacade, Files.asResource(resource.getFile()), item); } } } /** * Set the flag that determines whether to save internal data for {@link ExecutionContext}. Only switch this to false if you don't want to save * any state from this stream, and you don't need it to be restartable. Always set it to false if the reader is being used in a concurrent * environment. * * @param saveState flag value (default true). */ public void setSaveState(boolean saveState) { this.saveState = saveState; } /** * The flag that determines whether to save internal state for restarts. * * @return true if the flag was set */ public boolean isSaveState() { return saveState; } }