Java tutorial
/* (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.tasklet; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; import java.util.logging.Level; import java.util.logging.Logger; import org.geoserver.backuprestore.Backup; import org.geoserver.backuprestore.BackupRestoreItem; import org.geoserver.backuprestore.utils.BackupUtils; import org.geoserver.catalog.CoverageInfo; import org.geoserver.catalog.CoverageStoreInfo; import org.geoserver.catalog.DataStoreInfo; import org.geoserver.catalog.FeatureTypeInfo; import org.geoserver.catalog.LayerInfo; import org.geoserver.catalog.ResourceInfo; import org.geoserver.catalog.StoreInfo; import org.geoserver.catalog.WorkspaceInfo; import org.geoserver.config.ServiceInfo; import org.geoserver.config.util.XStreamServiceLoader; import org.geoserver.platform.GeoServerExtensions; import org.geoserver.platform.resource.Resource; import org.geoserver.platform.resource.Resource.Type; import org.geoserver.platform.resource.ResourceStore; import org.geoserver.platform.resource.Resources; import org.geoserver.platform.resource.Resources.AnyFilter; import org.geoserver.util.Filter; import org.geotools.filter.text.ecql.ECQL; import org.geotools.util.logging.Logging; import org.jdom2.Document; import org.jdom2.Element; import org.jdom2.output.Format; import org.jdom2.output.XMLOutputter; import org.springframework.batch.core.ExitStatus; import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobInterruptedException; import org.springframework.batch.core.StepContribution; import org.springframework.batch.core.StepExecution; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.core.step.tasklet.StoppableTasklet; import org.springframework.batch.repeat.RepeatStatus; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.core.task.TaskExecutor; import org.springframework.util.Assert; /** * Base Class for Backup and Restore custom Tasklets. <br> * Exposes some utility methods to correctly marshall/unmarshall items to/from backup folder. * * <p>The logic is executed asynchronously using injected {@link #setTaskExecutor(TaskExecutor)} - * timeout value is required to be set, so that the batch job does not hang forever if the external * process hangs. * * <p>Tasklet periodically checks for termination status (i.e. {@link * #doExecute(StepContribution,ChunkContext,JobExecution)} finished its execution or {@link * #setTimeout(long)} expired or job was interrupted). The check interval is given by {@link * #setTerminationCheckInterval(long)}. * * <p>When job interrupt is detected tasklet's execution is terminated immediately by throwing * {@link JobInterruptedException}. * * <p>{@link #setInterruptOnCancel(boolean)} specifies whether the tasklet should attempt to * interrupt the thread that executes the system command if it is still running when tasklet exits * (abnormally). * * @author Robert Kasanicky * @author Will Schipp * @author Alessio Fabiani, GeoSolutions */ @SuppressWarnings("rawtypes") public abstract class AbstractCatalogBackupRestoreTasklet<T> extends BackupRestoreItem implements StoppableTasklet, InitializingBean { protected static Logger LOGGER = Logging.getLogger(AbstractCatalogBackupRestoreTasklet.class); /* * */ protected static Map<String, Filter<Resource>> resources = new HashMap<String, Filter<Resource>>(); /* * */ static { resources.put("/", new Filter<Resource>() { @Override public boolean accept(Resource res) { if (res.getType() == Type.DIRECTORY && !res.name().equalsIgnoreCase("temp") && !res.name().equalsIgnoreCase("tmp") && !res.name().equalsIgnoreCase("workspaces") || (res.getType() == Type.RESOURCE && (res.name().endsWith(".properties") || res.name().endsWith(".ini") || res.name().endsWith(".conf")))) { return true; } return false; } }); resources.put("demo", AnyFilter.INSTANCE); resources.put("images", AnyFilter.INSTANCE); resources.put("logs", new Filter<Resource>() { @Override public boolean accept(Resource res) { if (!res.name().endsWith(".xml")) { return true; } return false; } }); resources.put("gwc-layers", AnyFilter.INSTANCE); resources.put("layergroups", AnyFilter.INSTANCE); resources.put("palettes", AnyFilter.INSTANCE); resources.put("plugIns", AnyFilter.INSTANCE); // NOTE: it would be better to use ad-hoc Visitors in order to scan the // Style Resources and download only the ones needed. // This maybe an improvement for a future release/refactoring. resources.put("styles", new Filter<Resource>() { @Override public boolean accept(Resource res) { if (res.name().toLowerCase().endsWith("sld") || // exclude everything ends with SLD ext (SLD, YSLD, ...) res.name().toLowerCase().endsWith(".xml") || res.name().toLowerCase().endsWith(".css")) // exclude CSS also { return false; } return true; } }); resources.put("user_projections", AnyFilter.INSTANCE); resources.put("validation", AnyFilter.INSTANCE); resources.put("www", AnyFilter.INSTANCE); resources.put("csw", AnyFilter.INSTANCE); } private long timeout = 0; private long checkInterval = 1000; private StepExecution execution = null; private TaskExecutor taskExecutor = new SimpleAsyncTaskExecutor(); private boolean interruptOnCancel = false; private volatile boolean stopped = false; public static final String BR_INDEX_XML = "br_index.xml"; public AbstractCatalogBackupRestoreTasklet(Backup backupFacade) { super(backupFacade); } @Override public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { super.retrieveInterstepData(chunkContext.getStepContext().getStepExecution()); JobExecution jobExecution = chunkContext.getStepContext().getStepExecution().getJobExecution(); FutureTask<RepeatStatus> theTask = new FutureTask<RepeatStatus>(new Callable<RepeatStatus>() { @Override public RepeatStatus call() throws Exception { return doExecute(contribution, chunkContext, jobExecution); } }); long t0 = System.currentTimeMillis(); taskExecutor.execute(theTask); while (true) { Thread.sleep(checkInterval); // moved to the end of the logic JobExecution currentExecution = chunkContext.getStepContext().getStepExecution().getJobExecution(); if (currentExecution.isStopping()) { stopped = true; } if (theTask.isDone()) { return theTask.get(); } else if (System.currentTimeMillis() - t0 > timeout) { theTask.cancel(interruptOnCancel); JobInterruptedException exception = new JobInterruptedException( "Job " + currentExecution + " did not finish within the timeout."); logValidationExceptions((T) null, exception); return RepeatStatus.FINISHED; } else if (execution != null && execution.isTerminateOnly()) { theTask.cancel(interruptOnCancel); JobInterruptedException exception = new JobInterruptedException( "Job " + currentExecution + " interrupted while executing."); logValidationExceptions((T) null, exception); return RepeatStatus.FINISHED; } else if (stopped) { theTask.cancel(interruptOnCancel); contribution.setExitStatus(ExitStatus.STOPPED); return RepeatStatus.FINISHED; } } } /** * @param contribution * @param chunkContext * @param jobExecution * @return * @throws Exception */ abstract RepeatStatus doExecute(StepContribution contribution, ChunkContext chunkContext, JobExecution jobExecution) throws Exception; /** * @param resourceStore * @param baseDir * @throws Exception * @throws IOException */ public void backupRestoreAdditionalResources(ResourceStore resourceStore, Resource baseDir) throws Exception { try { for (Entry<String, Filter<Resource>> entry : resources.entrySet()) { Resource resource = resourceStore.get(entry.getKey()); if (resource != null && Resources.exists(resource)) { List<Resource> resources = Resources.list(resource, entry.getValue(), false); Resource targetDir = BackupUtils.dir(baseDir, resource.name()); for (Resource res : resources) { try { if (res.getType() != Type.DIRECTORY) { Resources.copy(res.file(), targetDir); } else { Resources.copy(res, BackupUtils.dir(targetDir, res.name())); } } catch (Exception e) { LOGGER.log(Level.WARNING, "Error occurred while trying to move a Resource!", e); if (getCurrentJobExecution() != null) { getCurrentJobExecution().addWarningExceptions(Arrays.asList(e)); } } } } } } catch (Exception e) { logValidationExceptions((T) null, e); } } // @SuppressWarnings({ "unchecked", "static-access" }) public void doWrite(Object item, Resource directory, String fileName) throws Exception { try { if (item instanceof ServiceInfo) { ServiceInfo service = (ServiceInfo) item; XStreamServiceLoader loader = findServiceLoader(service); try { loader.save(service, backupFacade.getGeoServer(), BackupUtils.dir(directory, fileName)); } catch (Throwable t) { throw new RuntimeException(t); // LOGGER.log(Level.SEVERE, "Error occurred while saving configuration", t); } } else { // unwrap dynamic proxies OutputStream out = Resources.fromPath(fileName, directory).out(); try { if (getXp() == null) { xstream = getxStreamPersisterFactory().createXMLPersister(); setXp(xstream.getXStream()); } item = xstream.unwrapProxies(item); getXp().toXML(item, out); } finally { out.close(); } } } catch (Exception e) { logValidationExceptions((T) item, e); } } // @SuppressWarnings({ "unchecked" }) public Object doRead(Resource directory, String fileName) throws Exception { Object item = null; try { InputStream in = Resources.fromPath(fileName, directory).in(); // Try first using the Services Loaders final List<XStreamServiceLoader> loaders = GeoServerExtensions.extensions(XStreamServiceLoader.class); for (XStreamServiceLoader<ServiceInfo> l : loaders) { try { if (l.getFilename().equals(fileName)) { item = l.load(backupFacade.getGeoServer(), Resources.fromPath(fileName, directory)); if (item != null && item instanceof ServiceInfo) { return item; } } } catch (Exception e) { // Just skip and try with another loader item = null; } } try { if (item == null) { try { if (getXp() == null) { xstream = getxStreamPersisterFactory().createXMLPersister(); setXp(xstream.getXStream()); } item = getXp().fromXML(in); } finally { in.close(); } } } catch (Exception e) { // Collect warnings item = null; if (getCurrentJobExecution() != null) { getCurrentJobExecution().addWarningExceptions(Arrays.asList(e)); } } } catch (Exception e) { logValidationExceptions((T) null, e); } return item; } /** * This method dumps the current Backup index: - List of Workspaces - List of Stores - List of * Layers * * @param sourceFolder * @throws IOException */ protected void dumpBackupIndex(Resource sourceFolder) throws IOException { Element root = new Element("Index"); Document doc = new Document(); for (WorkspaceInfo ws : getCatalog().getWorkspaces()) { if (!filteredResource(ws, false)) { Element workspace = new Element("Workspace"); workspace.addContent(new Element("Name").addContent(ws.getName())); root.addContent(workspace); for (DataStoreInfo ds : getCatalog().getStoresByWorkspace(ws.getName(), DataStoreInfo.class)) { if (!filteredResource(ds, ws, true, StoreInfo.class)) { Element store = new Element("Store"); store.setAttribute("type", "DataStoreInfo"); store.addContent(new Element("Name").addContent(ds.getName())); workspace.addContent(store); for (FeatureTypeInfo ft : getCatalog().getFeatureTypesByDataStore(ds)) { if (!filteredResource(ft, ws, true, ResourceInfo.class)) { for (LayerInfo ly : getCatalog().getLayers(ft)) { if (!filteredResource(ly, ws, true, LayerInfo.class)) { Element layer = new Element("Layer"); layer.setAttribute("type", "VECTOR"); layer.addContent(new Element("Name").addContent(ly.getName())); store.addContent(layer); } } } } } } for (CoverageStoreInfo cs : getCatalog().getStoresByWorkspace(ws.getName(), CoverageStoreInfo.class)) { if (!filteredResource(cs, ws, true, StoreInfo.class)) { Element store = new Element("Store"); store.setAttribute("type", "CoverageStoreInfo"); store.addContent(new Element("Name").addContent(cs.getName())); workspace.addContent(store); for (CoverageInfo ci : getCatalog().getCoveragesByCoverageStore(cs)) { if (!filteredResource(ci, ws, true, ResourceInfo.class)) { for (LayerInfo ly : getCatalog().getLayers(ci)) { if (!filteredResource(ly, ws, true, LayerInfo.class)) { Element layer = new Element("Layer"); layer.setAttribute("type", "RASTER"); layer.addContent(new Element("Name").addContent(ly.getName())); store.addContent(layer); } } } } } } } } if (filterIsValid()) { Element filter = new Element("Filters"); if (getFilters().length > 0 && getFilters()[0] != null) { Element wsFilter = new Element("Filter"); wsFilter.setAttribute("type", "WorkspaceInfo"); wsFilter.addContent(new Element("ECQL").addContent(ECQL.toCQL(getFilters()[0]))); filter.addContent(wsFilter); } if (getFilters().length > 1 && getFilters()[1] != null) { Element siFilter = new Element("Filter"); siFilter.setAttribute("type", "StoreInfo"); siFilter.addContent(new Element("ECQL").addContent(ECQL.toCQL(getFilters()[1]))); filter.addContent(siFilter); } if (getFilters().length > 2 && getFilters()[2] != null) { Element liFilter = new Element("Filter"); liFilter.setAttribute("type", "LayerInfo"); liFilter.addContent(new Element("ECQL").addContent(ECQL.toCQL(getFilters()[2]))); filter.addContent(liFilter); } root.addContent(filter); } doc.setRootElement(root); XMLOutputter outter = new XMLOutputter(); outter.setFormat(Format.getPrettyFormat()); outter.output(doc, new FileWriter(sourceFolder.get(BR_INDEX_XML).file())); } @SuppressWarnings({ "unchecked" }) protected XStreamServiceLoader findServiceLoader(ServiceInfo service) { XStreamServiceLoader loader = null; final List<XStreamServiceLoader> loaders = GeoServerExtensions.extensions(XStreamServiceLoader.class); for (XStreamServiceLoader<ServiceInfo> l : loaders) { if (l.getServiceClass().isInstance(service)) { loader = l; break; } } if (loader == null) { throw new IllegalArgumentException("No loader for " + service.getName()); } return loader; } @Override public void afterPropertiesSet() throws Exception { Assert.notNull(backupFacade, "backupFacade must be set"); Assert.notNull(getxStreamPersisterFactory(), "xstream must be set"); Assert.isTrue(timeout > 0, "timeout value must be greater than zero"); Assert.notNull(taskExecutor, "taskExecutor is required"); } /** * Timeout in milliseconds. * * @param timeout upper limit for how long the execution of the external program is allowed to * last. */ public void setTimeout(long timeout) { this.timeout = timeout; } /** * The time interval how often the tasklet will check for termination status. * * @param checkInterval time interval in milliseconds (1 second by default). */ public void setTerminationCheckInterval(long checkInterval) { this.checkInterval = checkInterval; } /** * Sets the task executor that will be used to execute the system command NB! Avoid using a * synchronous task executor */ public void setTaskExecutor(TaskExecutor taskExecutor) { this.taskExecutor = taskExecutor; } /** * If <code>true</code> tasklet will attempt to interrupt the thread executing the system * command if {@link #setTimeout(long)} has been exceeded or user interrupts the job. <code> * false</code> by default */ public void setInterruptOnCancel(boolean interruptOnCancel) { this.interruptOnCancel = interruptOnCancel; } /** * Will interrupt the thread executing the system command only if {@link * #setInterruptOnCancel(boolean)} has been set to true. Otherwise the underlying command will * be allowed to finish before the tasklet ends. * * @since 3.0 * @see StoppableTasklet#stop() */ @Override public void stop() { stopped = true; } }