All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.n52.io.PreRenderingJob Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2013-2019 52°North Initiative for Geospatial Open Source
 * Software GmbH
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 as published
 * by the Free Software Foundation.
 *
 * If the program is linked with libraries which are licensed under one of
 * the following licenses, the combination of the program with the linked
 * library is not considered a "derivative work" of the program:
 *
 *     - Apache License, version 2.0
 *     - Apache Software License, version 1.0
 *     - GNU Lesser General Public License, version 3
 *     - Mozilla Public License, versions 1.0, 1.1 and 2.0
 *     - Common Development and Distribution License (CDDL), version 1.0
 *
 * Therefore the distribution of the program linked with libraries licensed
 * under the aforementioned licenses, is permitted by the copyright holders
 * if the distribution is compliant with both the GNU General Public License
 * version 2 and the aforementioned licenses.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
 * for more details.
 */
package org.n52.io;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.imageio.ImageIO;
import javax.servlet.ServletConfig;

import org.joda.time.DateTime;
import org.joda.time.Interval;
import org.n52.io.PrerenderingJobConfig.RenderingConfig;
import org.n52.io.handler.DatasetFactoryException;
import org.n52.io.handler.DefaultIoFactory;
import org.n52.io.handler.IoHandlerException;
import org.n52.io.handler.IoHandlerFactory;
import org.n52.io.request.IoParameters;
import org.n52.io.request.Parameters;
import org.n52.io.response.dataset.AbstractValue;
import org.n52.io.response.dataset.Data;
import org.n52.io.response.dataset.DatasetOutput;
import org.n52.io.response.dataset.quantity.QuantityValue;
import org.n52.io.task.ScheduledJob;
import org.n52.series.spi.srv.DataService;
import org.n52.series.spi.srv.ParameterService;
import org.n52.web.common.Stopwatch;
import org.n52.web.exception.ResourceNotFoundException;
import org.quartz.InterruptableJob;
import org.quartz.JobBuilder;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.UnableToInterruptJobException;
import org.quartz.utils.FindbugsSuppressWarnings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.context.ServletConfigAware;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class PreRenderingJob extends ScheduledJob implements InterruptableJob, ServletConfigAware {

    private static final Logger LOGGER = LoggerFactory.getLogger(PreRenderingJob.class);

    private static final int WIDTH_DEFAULT = 800;
    private static final int HEIGHT_DEFAULT = 500;
    private static final String LANGUAGE_DEFAULT = "en";
    private static final boolean GRID_DEFAULT = true;
    private static final boolean LEGEND_DEFAULT = false;
    private static final boolean GENERALIZE_DEFAULT = false;

    private static final String JOB_DATA_CONFIG_FILE = "configFile";
    private static final String JOB_DATA_WEBAPP_FOLDER = "webappFolder";
    private static final String IMAGE_EXTENSION = "png";

    @Autowired
    @Qualifier("datasetService")
    // autowired due to quartz job creation
    private ParameterService>> datasetService;

    @Autowired
    @Qualifier("datasetService")
    // autowired due to quartz job creation
    private DataService>> dataService;

    private PrerenderingJobConfig taskConfigPrerendering;

    private String webappFolder;

    private String configFile;

    private boolean interrupted;

    @FindbugsSuppressWarnings("OBL_UNSATISFIED_OBLIGATION")
    private PrerenderingJobConfig readJobConfig(String file) {
        try (InputStream taskConfig = getClass().getResourceAsStream(file)) {
            ObjectMapper om = new ObjectMapper();
            return om.readValue(taskConfig, PrerenderingJobConfig.class);
        } catch (IOException e) {
            LOGGER.error("Could not load {}. Using empty config.", file, e);
            return new PrerenderingJobConfig();
        }
    }

    @Override
    public JobDetail createJobDetails() {
        return JobBuilder.newJob(PreRenderingJob.class)
                         .withIdentity(getJobName())
                         .withDescription(getJobDescription())
                         .usingJobData(JOB_DATA_CONFIG_FILE, configFile)
                         .usingJobData(JOB_DATA_WEBAPP_FOLDER, webappFolder)
                         .build();
    }

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        if (interrupted) {
            return;
        }

        LOGGER.info("Start prerendering task");
        final Stopwatch stopwatch = Stopwatch.startStopwatch();
        final JobDetail details = context.getJobDetail();
        JobDataMap jobDataMap = details.getJobDataMap();
        taskConfigPrerendering = readJobConfig(jobDataMap.getString(JOB_DATA_CONFIG_FILE));
        webappFolder = jobDataMap.getString(JOB_DATA_WEBAPP_FOLDER);

        List phenomenonStyles = taskConfigPrerendering.getPhenomenonStyles();
        List styles = taskConfigPrerendering.getDatasetStyles();
        for (RenderingConfig config : phenomenonStyles) {
            Map parameters = new HashMap<>();
            parameters.put("phenomenon", config.getId());
            IoParameters query = IoParameters.createFromSingleValueMap(parameters);
            for (DatasetOutput< ? > metadata : datasetService.getCondensedParameters(query)) {
                String timeseriesId = metadata.getId();
                renderConfiguredIntervals(timeseriesId, config);
                if (interrupted) {
                    return;
                }
            }
        }

        for (RenderingConfig config : styles) {
            renderConfiguredIntervals(config.getId(), config);

            if (interrupted) {
                return;
            }
        }

        LOGGER.debug("prerendering took '{}'", stopwatch.stopInSeconds());
    }

    private void renderConfiguredIntervals(String datasetId, RenderingConfig style) {
        try {
            for (String interval : style.getInterval()) {
                renderWithStyle(datasetId, style, interval);
            }
        } catch (Throwable e) {
            LOGGER.error("Error occured while prerendering timeseries {}.", datasetId, e);
        }
    }

    private void renderWithStyle(String datasetId, RenderingConfig renderingConfig, String interval)
            throws IOException, DatasetFactoryException, URISyntaxException {
        IntervalWithTimeZone timespan = createTimespanFromInterval(datasetId, interval);
        IoParameters parameters = createConfig(datasetId, timespan.toString(), renderingConfig);

        String chartQualifier = renderingConfig.getChartQualifier();
        FileOutputStream fos = createFile(datasetId, interval, chartQualifier);

        try (FileOutputStream out = fos;) {
            createIoFactory(parameters).createHandler(IMAGE_EXTENSION)
                                       .writeBinary(out);
            fos.flush();
        } catch (IoHandlerException | IOException e) {
            LOGGER.error("Image creation occures error.", e);
        }
    }

    private IoHandlerFactory>,
                      AbstractValue< ? >> createIoFactory(IoParameters parameters)
                              throws DatasetFactoryException, URISyntaxException, MalformedURLException {
        return createDefaultIoFactory().create(QuantityValue.TYPE)
                                       .setParameters(parameters)
                                       .setDataService(dataService)
                                       .setDatasetService(datasetService);
    }

    private DefaultIoFactory>,
                             AbstractValue< ? >> createDefaultIoFactory() {
        return new DefaultIoFactory<>();
    }

    @Override
    public void interrupt() throws UnableToInterruptJobException {
        interrupted = true;
        LOGGER.info("Marked job to interrupt.");
    }

    @Override
    public void setServletConfig(ServletConfig servletConfig) {
        webappFolder = servletConfig.getServletContext()
                                    .getRealPath("/");
    }

    public String getConfigFile() {
        return configFile;
    }

    public void setConfigFile(String configFile) {
        this.configFile = configFile;
    }

    public List getPrerenderedImages(final String datasetId) {
        if (taskConfigPrerendering == null) {
            taskConfigPrerendering = readJobConfig(configFile);
        }
        Path outputPath = getOutputFolder();
        ArrayList files = new ArrayList<>();
        File outputDir = outputPath.toFile();
        if (outputDir.isDirectory()) {
            FilenameFilter startsWithIdFilter = (dir, name) -> name.startsWith(datasetId);
            String[] filtered = outputDir.list(startsWithIdFilter);
            if (filtered != null) {
                files.addAll(Arrays.asList(filtered));
            }
        }
        return files;
    }

    public boolean hasPrerenderedImage(String fileName) {
        return hasPrerenderedImage(fileName, null);
    }

    public boolean hasPrerenderedImage(String datasetId, String chartQualifier) {
        return createFileName(datasetId, chartQualifier).exists();
    }

    public void writePrerenderedGraphToOutputStream(String filename, OutputStream outputStream) {
        writePrerenderedGraphToOutputStream(filename, null, outputStream);
    }

    public void writePrerenderedGraphToOutputStream(String datasetId, String qualifier, OutputStream outputStream) {
        if (taskConfigPrerendering == null) {
            taskConfigPrerendering = readJobConfig(configFile);
        }
        try {
            BufferedImage image = loadImage(datasetId, qualifier);
            if (image == null) {
                ResourceNotFoundException ex = new ResourceNotFoundException("Could not find image on server.");
                ex.addHint("Perhaps the image is being rendered at the moment. Try again later.");
                throw ex;
            }
            LOGGER.debug("write prerendered image '{}'", createFileName(datasetId, qualifier));
            ImageIO.write(image, IMAGE_EXTENSION, outputStream);
        } catch (IOException e) {
            LOGGER.error("Error while loading pre rendered image", e);
        }
    }

    private BufferedImage loadImage(String datasetId, String qualifier) throws IOException {
        return ImageIO.read(new FileInputStream(createFileName(datasetId, qualifier)));
    }

    private IntervalWithTimeZone createTimespanFromInterval(String datasetId, String period) {
        DateTime now = new DateTime();
        if (period.equals("lastDay")) {
            Interval interval = new Interval(now.minusDays(1), now);
            return new IntervalWithTimeZone(interval.toString());
        } else if (period.equals("lastWeek")) {
            Interval interval = new Interval(now.minusWeeks(1), now);
            return new IntervalWithTimeZone(interval.toString());
        } else if (period.equals("lastMonth")) {
            Interval interval = new Interval(now.minusMonths(1), now);
            return new IntervalWithTimeZone(interval.toString());
        } else {
            throw new ResourceNotFoundException("Unknown interval '" + period + "' for datatset " + datasetId);
        }
    }

    private FileOutputStream createFile(String datasetId, String interval, String postfix) throws IOException {
        String chartQualifier = postfix != null
                ? interval + "_" + postfix
                : interval;
        File file = createFileName(datasetId, chartQualifier);
        if (!file.exists() && !file.createNewFile()) {
            LOGGER.warn("Can't create file '{}'", file.getAbsolutePath());
        }
        if (!file.setLastModified(new Date().getTime())) {
            LOGGER.debug("Can't set last modified date at '{}'", file.getAbsolutePath());
        }
        return new FileOutputStream(file);
    }

    private File createFileName(String datasetId, String qualifier) {
        if (taskConfigPrerendering == null) {
            taskConfigPrerendering = readJobConfig(configFile);
        }
        Path outputDirectory = getOutputFolder();
        String filename = qualifier != null
                ? datasetId + "_" + qualifier
                : datasetId;
        return outputDirectory.resolve(filename + ".png")
                              .toFile();
    }

    private Path getOutputFolder() {
        final Map generalConfig = taskConfigPrerendering.getGeneralConfig();
        String outputPath = generalConfig.get("outputPath");
        Path outputDirectory = Paths.get(webappFolder)
                                    .resolve(outputPath);
        File dir = outputDirectory.toFile();
        if (!dir.exists() && !dir.mkdirs()) {
            LOGGER.warn("Unable to create output folder '{}'.", outputDirectory);
        }
        return outputDirectory;
    }

    private IoParameters createConfig(String datasetId, String interval, RenderingConfig renderingConfig) {
        Map configuration = new HashMap<>();

        // set defaults
        configuration.put("width", Integer.toString(WIDTH_DEFAULT));
        configuration.put("height", Integer.toString(HEIGHT_DEFAULT));
        configuration.put("grid", Boolean.toString(GRID_DEFAULT));
        configuration.put("legend", Boolean.toString(LEGEND_DEFAULT));
        configuration.put("generalize", Boolean.toString(GENERALIZE_DEFAULT));
        configuration.put("locale", LANGUAGE_DEFAULT);
        configuration.put("timespan", interval);

        // overrides the above defaults (from json config)
        configuration.putAll(taskConfigPrerendering.getGeneralConfig());
        if (renderingConfig.getConfig() != null) {
            configuration.putAll(renderingConfig.getConfig());
        }

        // set flag to mark this config comes from prerendering
        configuration.put(IoParameters.RENDERING_TRIGGER, "prerendering");

        try {
            ObjectMapper om = new ObjectMapper();
            configuration.put(Parameters.DATASETS, datasetId);
            configuration.put(Parameters.STYLE, om.writeValueAsString(renderingConfig.getStyle()));
            configuration.put("title", renderingConfig.getTitle());
        } catch (JsonProcessingException e) {
            LOGGER.warn("Invalid rendering style.", e);
        }

        return IoParameters.createFromSingleValueMap(configuration);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy