Java tutorial
/* * Copyright 2016 Danish Maritime Authority. * * 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 org.niord.core.batch; import com.fasterxml.jackson.core.type.TypeReference; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.niord.core.batch.vo.BatchSetVo; import org.niord.core.repo.RepositoryService; import org.niord.core.util.JsonUtils; import org.slf4j.Logger; import javax.annotation.PostConstruct; import javax.annotation.Resource; import javax.ejb.Schedule; import javax.ejb.Singleton; import javax.ejb.Startup; import javax.ejb.Timeout; import javax.ejb.Timer; import javax.ejb.TimerConfig; import javax.ejb.TimerService; import javax.inject.Inject; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Serializable; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; /** * A <i>batch set</i> is a folder or a zipped archive that contains the following files: * <ul> * <li>batch-set.json: Contains an array of BatchSetVo instances, each representing * a batch job</li> * <li>The batch job data files referenced in the batch-set.json file</li> * </ul> * <p> * A batch set can either be uploaded from the Admin -> Batch Jobs page or * via a "niord.batch-set" System setting. */ @Singleton @Startup @SuppressWarnings("unused") public class BatchSetService { public static final String BATCH_SETS_FOLDER = "batch-sets"; @Inject Logger log; @Resource TimerService timerService; @Inject BatchService batchService; @Inject RepositoryService repositoryService; /** * Check if a batch set has been specified via the "niord.batch-set" system setting **/ @PostConstruct public void init() { if (StringUtils.isNotBlank(System.getProperty("niord.batch-set"))) { Path path = Paths.get(System.getProperty("niord.batch-set")); try { executeBatchSetFromArchiveOrFolder(path); } catch (Exception e) { log.error("Error reading batch set from folder " + path, e); } } } /** * Executes the batch set from the given zip archive or folder * @param path the archive or folder */ private void executeBatchSetFromArchiveOrFolder(Path path) throws Exception { if (Files.isRegularFile(path)) { // Zip archive try (FileInputStream in = new FileInputStream(path.toFile())) { extractAndExecuteBatchSetArchive(in, null); } } else if (Files.isDirectory(path)) { // Exploded folder BatchSetSpecification batchSetSpec = readBatchSetFromFolder(path); executeBatchSet(batchSetSpec, null); } else { throw new Exception("Non-existing batch set archive or folder " + path); } } /** * Reads the batch set specification from the given folder * @param folder the folder * @return the batch set specification */ private BatchSetSpecification readBatchSetFromFolder(Path folder) throws Exception { if (!Files.isDirectory(folder)) { throw new Exception("Non-existing batch set folder " + folder); } // Read the batch-set.json file Path batchSetFile = folder.resolve("batch-set.json"); if (!Files.isRegularFile(batchSetFile)) { throw new Exception("Batch set did not contain a batch-set.json file"); } List<BatchSetVo> batchSetItems = JsonUtils.readJson(new TypeReference<List<BatchSetVo>>() { }, batchSetFile); return new BatchSetSpecification(folder, batchSetItems); } /** Called to start a batch set execution from a zipped batch set archive **/ @SuppressWarnings("all") public void extractAndExecuteBatchSetArchive(InputStream inputStream, StringBuilder txt) throws Exception { // Create a temporary destination folder String tempArchiveRepoPath = repositoryService.getNewTempDir().getPath(); Path folder = repositoryService.getRepoRoot().resolve(tempArchiveRepoPath); Files.createDirectories(folder); // Unzip the archive log.info("Unzipping batch set zip archive to folder " + folder); //get the zip file content ZipInputStream zis = new ZipInputStream(inputStream); ZipEntry entry = zis.getNextEntry(); while (entry != null) { File entryDestination = new File(folder.toFile(), entry.getName()); if (entry.isDirectory()) { entryDestination.mkdirs(); } else { entryDestination.getParentFile().mkdirs(); try (OutputStream out = new FileOutputStream(entryDestination)) { IOUtils.copy(zis, out); } } entry = zis.getNextEntry(); } zis.closeEntry(); // Execute the batch set BatchSetSpecification batchSetSpec = readBatchSetFromFolder(folder); executeBatchSet(batchSetSpec, txt); } /** * Starts executing a batch set by scheduling each batch job of the specification * @param batchSetSpec the batch set specification */ private void executeBatchSet(BatchSetSpecification batchSetSpec, StringBuilder txt) { txt = txt != null ? txt : new StringBuilder(); for (BatchSetVo batchSetItem : batchSetSpec.getBatchSetItems()) { BatchSetExecution execution = new BatchSetExecution(batchSetSpec, batchSetItem); long delay = batchSetItem.getDelay(); txt.append("Scheduling batch job ").append(batchSetItem.getJobName()).append(" in ").append(delay) .append(" ms\n"); timerService.createSingleActionTimer(delay, new TimerConfig(execution, false)); } } /** * Called in order to execute a batch set item */ @Timeout private void executeBatchSetItem(Timer timer) { BatchSetExecution batchSetExecution = (BatchSetExecution) timer.getInfo(); String batchJobName = batchSetExecution.getBatchSetItem().getJobName(); String batchFileName = batchSetExecution.getBatchSetItem().getFileName(); Path file = batchSetExecution.getBatchSetSpec().getFolder().resolve(batchFileName); if (!Files.isRegularFile(file)) { log.error("Batch file for batch set item " + batchJobName + " did not exist: " + file); return; } Map<String, Object> properties = batchSetExecution.getBatchSetItem().getProperties() != null ? batchSetExecution.getBatchSetItem().getProperties() : new HashMap<>(); try (InputStream in = new FileInputStream(file.toFile())) { batchService.startBatchJobWithDataFile(batchJobName, in, batchFileName, properties); } catch (Exception e) { log.error("Error executing batch set item " + batchJobName + " from file " + file); } } /*****************************************/ /** Batch "batch-set" folder monitoring **/ /*****************************************/ /** * Called every minute to monitor the "batch-sets" folder. If a batch-set zip file has been * placed in this folder, the batch-set gets executed. */ @Schedule(persistent = false, second = "24", minute = "*/1", hour = "*/1") protected void monitorBatchJobInFolderInitiation() { Path batchSetsFolder = batchService.getBatchJobRoot().resolve(BATCH_SETS_FOLDER); if (Files.isDirectory(batchSetsFolder)) { try (DirectoryStream<Path> stream = Files.newDirectoryStream(batchSetsFolder)) { for (Path p : stream) { if (Files.isReadable(p) && Files.isRegularFile(p)) { try { executeBatchSetFromArchiveOrFolder(p); } catch (Exception e) { log.error("Error executing batch set " + p, e); } finally { // Delete the file try { Files.delete(p); } catch (IOException ignored) { } } } } } catch (IOException ignored) { } } } /***************************************/ /** Helper classes **/ /***************************************/ /** Defines the batch set to execute **/ public static class BatchSetSpecification implements Serializable { Path folder; List<BatchSetVo> batchSetItems = new ArrayList<>(); public BatchSetSpecification() { } public BatchSetSpecification(Path folder, List<BatchSetVo> batchSetItems) { this.folder = folder; this.batchSetItems = batchSetItems; } public Path getFolder() { return folder; } public void setFolder(Path folder) { this.folder = folder; } public List<BatchSetVo> getBatchSetItems() { return batchSetItems; } public void setBatchSetItems(List<BatchSetVo> batchSetItems) { this.batchSetItems = batchSetItems; } } /** Used for collecting information needed to execute a batch set item **/ public static class BatchSetExecution implements Serializable { BatchSetSpecification batchSetSpec; BatchSetVo batchSetItem; public BatchSetExecution() { } public BatchSetExecution(BatchSetSpecification batchSetSpec, BatchSetVo batchSetItem) { this.batchSetSpec = batchSetSpec; this.batchSetItem = batchSetItem; } public BatchSetSpecification getBatchSetSpec() { return batchSetSpec; } public void setBatchSetSpec(BatchSetSpecification batchSetSpec) { this.batchSetSpec = batchSetSpec; } public BatchSetVo getBatchSetItem() { return batchSetItem; } public void setBatchSetItem(BatchSetVo batchSetItem) { this.batchSetItem = batchSetItem; } } }