/* * Constellation - An open source and standard compliant SDI * http://www.constellation-sdi.org * * Copyright 2014 Geomatys. * * 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.constellation.metadata; import org.apache.sis.util.logging.Logging; import org.apache.sis.xml.MarshallerPool; import org.constellation.configuration.HarvestTask; import org.constellation.configuration.HarvestTasks; import org.constellation.metadata.harvest.CatalogueHarvester; import org.constellation.metadata.utils.MailSendingUtilities; import org.constellation.ws.CstlServiceException; import javax.mail.MessagingException; import javax.naming.NamingException; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Timer; import java.util.TimerTask; import java.util.logging.Level; import java.util.logging.Logger; /** * * @author Guilhem Legal (Geomatys) */ public class HarvestTaskSchreduler { /** * use for debugging purpose */ private static final Logger LOGGER = Logging.getLogger("org.constellation.metadata"); /** * The name of the harvest task file */ private static final String HARVEST_TASK_FILE_NAME = "HarvestTask.xml"; /** * A unMarshaller to get object from harvested resource. */ private final MarshallerPool marshallerPool; private final File configDir; /** * A list of scheduled Task (used in close method). */ private final List<Timer> schreduledTask = new ArrayList<Timer>(); private final CatalogueHarvester catalogueHarvester; public HarvestTaskSchreduler(final File configDir, final CatalogueHarvester catalogueHarvester) { MarshallerPool candidate = null; try { candidate = new MarshallerPool(JAXBContext.newInstance(HarvestTasks.class), null); } catch (JAXBException ex) { LOGGER.log(Level.SEVERE, null, ex); } this.marshallerPool = candidate; this.configDir = configDir; this.catalogueHarvester = catalogueHarvester; initializeHarvestTask(); } /** * Restore all the periodic Harvest task from the configuration file "HarvestTask.xml". * * @param configDirectory The configuration directory containing the file "HarvestTask.xml" */ private void initializeHarvestTask() { try { // we get the saved harvest task file final File f = new File(configDir, HARVEST_TASK_FILE_NAME); if (f.exists()) { final Unmarshaller unmarshaller = marshallerPool.acquireUnmarshaller(); final Object obj = unmarshaller.unmarshal(f); marshallerPool.recycle(unmarshaller); final Timer t = new Timer(); if (obj instanceof HarvestTasks) { final HarvestTasks tasks = (HarvestTasks) obj; for (HarvestTask task : tasks.getTask()) { final AsynchronousHarvestTask at = new AsynchronousHarvestTask(task.getSourceURL(), task.getResourceType(), task.getMode(), task.getEmails()); //we look for the time passed since the last harvest final long time = System.currentTimeMillis() - task.getLastHarvest(); long delay = 2000; if (time < task.getPeriod()) { delay = task.getPeriod() - time; } t.scheduleAtFixedRate(at, delay, task.getPeriod()); schreduledTask.add(t); } } else { LOGGER.severe("Bad data type for file HarvestTask.xml"); } } else { LOGGER.info("no Harvest task found (optionnal)"); } } catch (JAXBException e) { LOGGER.info("JAXB Exception while unmarshalling the file HarvestTask.xml"); } } /** * Save a periodic Harvest task into the specific configuration file "HarvestTask.xml". * This is made in order to restore the task when the server is shutdown and then restart. * * @param sourceURL The URL of the source to harvest. * @param resourceType The type of the resource. * @param mode The type of the source: 0 for a single record (ex: an xml file) 1 for a CSW service. * @param emails A list of mail addresses to contact when the Harvest is done. * @param period The time between each Harvest. * @param lastHarvest The time of the last task launch. */ private void saveSchreduledHarvestTask(final String sourceURL, final String resourceType, final int mode, final List<String> emails, final long period, final long lastHarvest) { final HarvestTask newTask = new HarvestTask(sourceURL, resourceType, mode, emails, period, lastHarvest); final File f = new File(configDir, HARVEST_TASK_FILE_NAME); try { final Marshaller marshaller = marshallerPool.acquireMarshaller(); if (f.exists()) { final Unmarshaller unmarshaller = marshallerPool.acquireUnmarshaller(); final Object obj = unmarshaller.unmarshal(f); marshallerPool.recycle(unmarshaller); if (obj instanceof HarvestTasks) { final HarvestTasks tasks = (HarvestTasks) obj; tasks.addTask(newTask); marshaller.marshal(tasks, f); } else { LOGGER.severe("Bad data type for file HarvestTask.xml"); } } else { final boolean created = f.createNewFile(); if (!created) { LOGGER.severe("unable to create a file HarevestTask.xml"); } else { final HarvestTasks tasks = new HarvestTasks(Arrays.asList(newTask)); marshaller.marshal(tasks, f); } } marshallerPool.recycle(marshaller); } catch (IOException ex) { LOGGER.severe("unable to create a file for schreduled harvest task"); } catch (JAXBException ex) { LOGGER.severe("A JAXB exception occurs when trying to marshall the shreduled harvest task"); LOGGER.log(Level.SEVERE, ex.getMessage(), ex); } } /** * Update the Harvest task file by recording the last Harvest date of a task. * This is made in order to avoid the systematic launch of all the task when the CSW start. * * @param sourceURL Used as the task identifier. * @param lastHarvest a long representing the last time where the task was launch */ private void updateSchreduledHarvestTask(final String sourceURL, final long lastHarvest) { final File f = new File(configDir, HARVEST_TASK_FILE_NAME); try { final Marshaller marshaller = marshallerPool.acquireMarshaller(); if (f.exists()) { final Unmarshaller unmarshaller = marshallerPool.acquireUnmarshaller(); final Object obj = unmarshaller.unmarshal(f); marshallerPool.recycle(unmarshaller); if (obj instanceof HarvestTasks) { final HarvestTasks tasks = (HarvestTasks) obj; final HarvestTask task = tasks.getTaskFromSource(sourceURL); if (task != null) { task.setLastHarvest(lastHarvest); marshaller.marshal(tasks, f); } } else { LOGGER.severe("Bad data type for file HarvestTask.xml"); } } else { LOGGER.severe("There is no Harvest task file to update"); } marshallerPool.recycle(marshaller); } catch (JAXBException ex) { LOGGER.severe("A JAXB exception occurs when trying to marshall the shreduled harvest task (update)"); LOGGER.log(Level.SEVERE, ex.getMessage(), ex); } } public void newAsynchronousHarvestTask(long period, final String sourceURL, final String resourceType, final int mode, final List<String> emails) { final Timer t = new Timer(); final TimerTask harvestTask = new AsynchronousHarvestTask(sourceURL, resourceType, mode, emails); //we launch only once the harvest if (period == 0) { t.schedule(harvestTask, 1000); //we launch the harvest periodically } else { t.scheduleAtFixedRate(harvestTask, 1000, period); schreduledTask.add(t); saveSchreduledHarvestTask(sourceURL, resourceType, mode, emails, period, System.currentTimeMillis() + 1000); } } public void destroy() { for (Timer t : schreduledTask) { t.cancel(); } } /** * A task launching an harvest periodically. */ class AsynchronousHarvestTask extends TimerTask { /** * The harvest mode SINGLE(0) or CATALOGUE(1) */ private final int mode; /** * The URL of the data source. */ private final String sourceURL; /** * The type of the resource (for single mode). */ private final String resourceType; /** * A list of email addresses. */ private final String[] emails; /** * Build a new Timer which will Harvest the source periodically. * */ public AsynchronousHarvestTask(final String sourceURL, final String resourceType, final int mode, final List<String> emails) { this.sourceURL = sourceURL; this.mode = mode; this.resourceType = resourceType; if (emails != null) { this.emails = emails.toArray(new String[emails.size()]); } else { this.emails = new String[0]; } } /** * This method is launch when the timer expire. */ @Override public void run() { LOGGER.log(Level.INFO, "launching harvest on:{0}", sourceURL); try { int[] results; if (mode == 0) { results = catalogueHarvester.harvestSingle(sourceURL, resourceType); } else { results = catalogueHarvester.harvestCatalogue(sourceURL); } updateSchreduledHarvestTask(sourceURL, System.currentTimeMillis()); /* TODO does we have to send a HarvestResponseType or a custom report to the mails addresses? TransactionSummaryType summary = new TransactionSummaryType(results[0], results[1], results[2], null); TransactionResponseType transactionResponse = new TransactionResponseType(summary, null, "2.0.2"); HarvestResponseType response = new HarvestResponseType(transactionResponse); */ final StringBuilder report = new StringBuilder("Harvest report:\n"); report.append("From: ").append(sourceURL).append('\n'); report.append("Inserted: ").append(results[0]).append('\n'); report.append("Updated: ").append(results[1]).append('\n'); report.append("Deleted: ").append(results[2]).append('\n'); report.append("at ").append(new Date(System.currentTimeMillis())); MailSendingUtilities.mail(emails, "Harvest report", report.toString()); } catch (SQLException ex) { LOGGER.log(Level.SEVERE, "The service has throw an SQLException: {0}", ex.getMessage()); } catch (JAXBException ex) { LOGGER.log(Level.SEVERE, "The resource can not be parsed: {0}", ex.getMessage()); } catch (MalformedURLException ex) { LOGGER.severe("The source URL is malformed"); } catch (IOException ex) { LOGGER.severe("The service can't open the connection to the source"); } catch (CstlServiceException ex) { LOGGER.log(Level.SEVERE, "Constellation exception:{0}", ex.getMessage()); } catch (MessagingException ex) { LOGGER.log(Level.SEVERE, "MessagingException exception:{0}", ex.getMessage()); } catch (NamingException ex) { LOGGER.log(Level.SEVERE, "Naming exception:{0}", ex.getMessage()); } } } }