Java tutorial
/* * (c) Kitodo. Key to digital objects e. V. <contact@kitodo.org> * * This file is part of the Kitodo project. * * It is licensed under GNU General Public License version 3 or later. * * For the full copyright and license information, please read the * GPL3-License.txt file that was distributed with this source code. */ package org.kitodo.production.services.image; import java.awt.Image; import java.awt.image.RenderedImage; import java.io.IOException; import java.io.OutputStream; import java.lang.reflect.UndeclaredThrowableException; import java.net.URI; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.imageio.ImageIO; import org.apache.commons.lang3.tuple.Pair; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.kitodo.api.imagemanagement.ImageFileFormat; import org.kitodo.config.xml.fileformats.FileFormat; import org.kitodo.data.database.beans.Folder; import org.kitodo.production.enums.GenerationMode; import org.kitodo.production.enums.ImageGeneratorStep; import org.kitodo.production.helper.tasks.EmptyTask; import org.kitodo.production.model.Subfolder; import org.kitodo.production.services.ServiceManager; import org.kitodo.production.services.file.FileService; import org.kitodo.production.thread.TaskImageGeneratorThread; import org.kitodo.production.thread.TaskScriptThread; /** * A program that generates images using the image management interface. This * program is run by the {@link TaskImageGeneratorThread} when the user manually * initiates the creation of the images. If the images are generated when the * task is completed, this is done by the {@link TaskScriptThread}. */ public class ImageGenerator implements Runnable { private static final Logger logger = LogManager.getLogger(ImageGenerator.class); private final FileService fileService = ServiceManager.getFileService(); private final ImageService imageService = ServiceManager.getImageService(); /** * Output folders. */ private Collection<Subfolder> outputs; /** * Current position in list. */ private int position; /** * Folder with source images. */ private Subfolder sourceFolder; /** * List of possible source images. */ private List<Pair<String, URI>> sources; /** * Current step of the generation process. */ private ImageGeneratorStep state; /** * Task in the TaskManager that runs this ImageGenerator. */ private EmptyTask supervisor; /** * List of elements to be generated. */ private List<ContentToBeGenerated> contentToBeGenerated; /** * Variant of image generation, see there. */ private GenerationMode mode; /** * Creates a new image generator. * * @param sourceFolder * image source folder * @param mode * whether all, or only a few images are to be generated * @param outputs * output folders to generate to */ public ImageGenerator(Subfolder sourceFolder, GenerationMode mode, Collection<Subfolder> outputs) { this.sourceFolder = sourceFolder; this.mode = mode; this.outputs = outputs; this.state = ImageGeneratorStep.LIST_SOURCE_FOLDER; this.sources = Collections.emptyList(); this.contentToBeGenerated = new LinkedList<>(); } /** * Appends the element to the list of elements to be generated. * * @param canonical * the canonical part of the file name * @param sourceURI * the source URI of the content to be generated * @param subfoldersWhoseContentsAreToBeGenerated * subfolders whose contents are to be generated */ public void addToContentToBeGenerated(String canonical, URI sourceURI, List<Subfolder> subfoldersWhoseContentsAreToBeGenerated) { contentToBeGenerated .add(new ContentToBeGenerated(canonical, sourceURI, subfoldersWhoseContentsAreToBeGenerated)); } /** * Invokes the image management interface method that creates a derivative. * What kind of derivative should be created is determined from the folder * configuration. * * @param sourceImage * address of the source image from which the derivative is to be * calculated. * @param imageProperties * configuration for the target image * @param imageFileFormat * image file format to create * @param destinationImage * image file to write * @throws IOException * if an underlying disk operation fails */ private void createDerivative(URI sourceImage, Folder imageProperties, ImageFileFormat imageFileFormat, URI destinationImage) throws IOException { imageService.createDerivative(sourceImage, imageProperties.getDerivative().get(), destinationImage, imageFileFormat); } /** * Generates a set of derivatives. * * @param instruction * Instruction, which pictures are to be generated. Left: image * source, right: destination folder. The image source consists * of the canonical part of the file name and the resolved file * name for the source image. The canonical part of the file name * is needed to calculate the corresponding file name in the * destination folder. The type of derivative to be generated is * defined in the properties of the destination folder. */ public void createDerivatives(ContentToBeGenerated instruction) { try { for (Subfolder destinationFolder : instruction.getSubfoldersWhoseContentsAreToBeGenerated()) { generateDerivative(instruction.getSourceURI(), destinationFolder, instruction.getCanonical()); } } catch (IOException e) { throw new UndeclaredThrowableException(e); } } /** * Generates a derived image and saves it with the on-board tools of Java. * The image is created by the image management interface. Which method of * the interface is called and its parameters are determined in the * configuration of the folder. The same is true for the file type under * which Java stores the image. * * @param sourceImage * reference to the image that serves as a template for the * reproduction process * @param imageProperties * folder settings define what an image is created * @param fileFormat * the file format specifies how the image should be saved * @param destinationImage * specifies the location where the image should be written * @throws IOException * if an underlying disk operation fails */ private void createImageWithImageIO(URI sourceImage, Folder imageProperties, FileFormat fileFormat, URI destinationImage) throws IOException { try (OutputStream outputStream = fileService.write(destinationImage)) { Image image = retrieveJavaImage(sourceImage, imageProperties); if (fileFormat.getFormatName().isPresent()) { ImageIO.write((RenderedImage) image, fileFormat.getFormatName().get(), outputStream); } } } /** * Determines the folders in which a derivative must be created. Because the * ModuleLoader does not work when invoked from a parallelStream(), we use a * classic loop here. * * @param canonical * canonical part of the file name, to determine the file names * in the destination folder * @return the images to be generated */ public List<Subfolder> determineFoldersThatNeedDerivatives(String canonical) { List<Subfolder> result = new ArrayList<>(outputs.size()); Predicate<? super Subfolder> requiresGeneration = mode.getFilter(canonical); for (Subfolder folder : outputs) { if (requiresGeneration.test(folder)) { result.add(folder); } } return result; } /** * Gets the file list from the content folder, converts it into the required * form, and stores it in the sources field. */ public void determineSources() { Map<String, URI> contents = sourceFolder.listContents(); Stream<Entry<String, URI>> contentsStream = contents.entrySet().stream(); Stream<Pair<String, URI>> sourcesStream = contentsStream .map(lambda -> Pair.of(lambda.getKey(), lambda.getValue())); this.sources = sourcesStream.collect(Collectors.toList()); } /** * Generates the derivative depending on the declared generator function. * * @param sourceImage * source file * @param destinationImage * path to the target file to be generated * @param canonical * the canonical part of the file name * @throws IOException * if filesystem I/O fails */ private void generateDerivative(URI sourceImage, Subfolder destinationImage, String canonical) throws IOException { Folder imageProperties = destinationImage.getFolder(); boolean isCreatingDerivative = imageProperties.getDerivative().isPresent(); boolean isChangingDpi = imageProperties.getDpi().isPresent(); boolean isGettingScaledWebImage = imageProperties.getImageScale().isPresent(); boolean isGettingSizedWebImage = imageProperties.getImageSize().isPresent(); if (isCreatingDerivative && destinationImage.getFileFormat().getImageFileFormat().isPresent()) { createDerivative(sourceImage, imageProperties, destinationImage.getFileFormat().getImageFileFormat().get(), destinationImage.getUri(canonical)); } else if (isChangingDpi || isGettingScaledWebImage || isGettingSizedWebImage) { createImageWithImageIO(sourceImage, imageProperties, destinationImage.getFileFormat(), destinationImage.getUri(canonical)); } } /** * Returns from contentToBeGenerated the item specified by position. * * @return the item specified by position */ public ContentToBeGenerated getFromContentToBeGeneratedByPosition() { return contentToBeGenerated.get(position); } /** * Returns the current position in the list. * * @return the position */ public int getPosition() { return position; } /** * Returns the list of source images. * * @return the list of source images */ public List<Pair<String, URI>> getSources() { return sources; } /** * Returns the list of image generation task descriptions. * * @return the list of image generation task descriptions */ public List<ContentToBeGenerated> getContentToBeGenerated() { return contentToBeGenerated; } /** * Returns the enum constant inicating the variant of the image generator * task. * * @return the variant of the image generator task */ public GenerationMode getMode() { return mode; } /** * If there is a supervisor, lets him take an action. Otherwise nothing * happens. * * @param action * what the supervisor should do */ public void letTheSupervisorDo(Consumer<EmptyTask> action) { if (Objects.nonNull(supervisor)) { action.accept(supervisor); } } /** * Invokes one of the three methods of the image management interface that * return a Java image. Which method is called and its parameters are * determined in the configuration of the folder. * * @param sourceImage * address of the source image from which the derivative is to be * calculated. * @param imageProperties * configuration for the target image * @return an image in memory * @throws IOException * if an underlying disk operation fails */ private Image retrieveJavaImage(URI sourceImage, Folder imageProperties) throws IOException { if (imageProperties.getDpi().isPresent()) { return imageService.changeDpi(sourceImage, imageProperties.getDpi().get()); } else if (imageProperties.getImageScale().isPresent()) { return imageService.getScaledWebImage(sourceImage, imageProperties.getImageScale().get()); } else if (imageProperties.getImageSize().isPresent()) { return imageService.getSizedWebImage(sourceImage, imageProperties.getImageSize().get()); } throw new IllegalArgumentException(imageProperties + " does not give any method to create a java image"); } /** * If the task is started, it will execute this run() method which will * start the export on the ExportDms. This task instance is passed in * addition so that the ExportDms can update the tasks state. * * @see org.kitodo.production.helper.tasks.EmptyTask#run() */ @Override public void run() { do { state.accept(this); setPosition(getPosition() + 1); setProgress(); if (Objects.nonNull(supervisor) && supervisor.isInterrupted()) { return; } } while (!(state.equals(ImageGeneratorStep.GENERATE_IMAGES) && getPosition() == getContentToBeGenerated().size())); logger.info("Completed"); } /** * Sets the current position in the list. * * @param position * position to set */ public void setPosition(int position) { this.position = position; } /** * Calculates and reports the progress of the task. */ private void setProgress() { if (Objects.nonNull(supervisor)) { int checked = state.equals(ImageGeneratorStep.GENERATE_IMAGES) ? getMode().equals(GenerationMode.ALL) ? 1 : sources.size() : 0; int generated = getMode().equals(GenerationMode.ALL) && state.equals(ImageGeneratorStep.DETERMINE_WHICH_IMAGES_NEED_TO_BE_GENERATED) ? 0 : getPosition(); int total = sources.size() + (getMode().equals(GenerationMode.ALL) ? 1 : getContentToBeGenerated().size()) + 1; supervisor.setProgress(100d * (1 + checked + generated) / total); } } /** * Sets the current processing state. * * @param state * state to set */ public void setState(ImageGeneratorStep state) { this.state = state; } /** * Set a supervisor for this activity. If a supervisor is set, the progress * is reported back to him, and he responds to his interrupt requests. * * @param supervisor * supervisor task to set */ public void setSupervisor(EmptyTask supervisor) { this.supervisor = supervisor; } }