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.goobi.production.flow.jobs; import de.sub.goobi.helper.Helper; import de.unigoettingen.sub.commons.util.file.FileUtils; import java.io.File; import java.io.IOException; import java.net.URI; import java.util.Calendar; import java.util.Date; import java.util.Iterator; import java.util.List; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.hibernate.HibernateException; import org.hibernate.Query; import org.hibernate.Session; import org.kitodo.data.database.beans.History; import org.kitodo.data.database.beans.Process; import org.kitodo.data.database.beans.Task; import org.kitodo.data.database.helper.enums.HistoryTypeEnum; import org.kitodo.data.exceptions.DataException; import org.kitodo.services.ServiceManager; /** * HistoryJob proofs History of {@link Process} and creates missing * {@link History}s * * @author Steffen Hankiewicz * @author Igor Toker * @version 15.06.2009 */ public class HistoryAnalyserJob extends AbstractGoobiJob { private static final Logger logger = LogManager.getLogger(HistoryAnalyserJob.class); private static final ServiceManager serviceManager = new ServiceManager(); /* * (non-Javadoc) * * @see org.goobi.production.flow.jobs.SimpleGoobiJob#initialize() */ @Override public String getJobName() { return "HistoryAnalyserJob"; } /* * (non-Javadoc) * * @see org.goobi.production.flow.jobs.SimpleGoobiJob#execute() */ @Override public void execute() { updateHistoryForAllProcesses(); } /** * Update the history if necessary. It means: - count storage difference in * byte <br> * - count imagesWork difference <br> * - count imagesMaster difference <br> * - count metadata difference <br> * - count docstruct difference <br> * * @param inProcess * the {@link Process} to use * * @return true, if any history event is updated, so the process has to be * saved to database */ public static Boolean updateHistory(Process inProcess) throws IOException, DataException { boolean updated = false; /* storage */ if (updateHistoryEvent(inProcess, HistoryTypeEnum.storageDifference, getCurrentStorageSize(inProcess))) { updated = true; } /* imagesWork */ Integer numberWork = FileUtils.getNumberOfFiles( new File(serviceManager.getProcessService().getImagesTifDirectory(true, inProcess)), ".tif"); if (updateHistoryEvent(inProcess, HistoryTypeEnum.imagesWorkDiff, numberWork.longValue())) { updated = true; } /* imagesMaster */ Integer numberMaster = FileUtils.getNumberOfFiles( new File(serviceManager.getProcessService().getImagesOrigDirectory(true, inProcess)), ".tif"); if (updateHistoryEvent(inProcess, HistoryTypeEnum.imagesMasterDiff, numberMaster.longValue())) { updated = true; } /* metadata */ if (updateHistoryEvent(inProcess, HistoryTypeEnum.metadataDiff, inProcess.getSortHelperMetadata().longValue())) { updated = true; } /* docstruct */ if (updateHistoryEvent(inProcess, HistoryTypeEnum.docstructDiff, inProcess.getSortHelperDocstructs().longValue())) { updated = true; } return updated; } /** * update history for each {@link Task} of given {@link Process}. * * @param inProcess * given {@link Process} * @return true, if changes are made and have to be saved to database */ @SuppressWarnings("incomplete-switch") private static Boolean updateHistoryForSteps(Process inProcess) { Boolean isDirty = false; History he = null; /* * These are the patterns, which must be set, if a pattern differs from * these something is wrong, timestamp pattern overrules status, in that * case status gets changed to match one of these pattern. * * <pre> status | begin in work work done * -------+------------------------------- 0 | null null null 1 | null * null null 2 | set set null 3 | set set set </pre> */ for (Task step : inProcess.getTasks()) { switch (step.getProcessingStatusEnum()) { case DONE: // fix missing start date if (step.getProcessingBegin() == null) { isDirty = true; if (step.getProcessingTime() == null) { step.setProcessingBegin(getTimestampFromPreviousStep(inProcess, step)); } else { step.setProcessingBegin(step.getProcessingTime()); } } // fix missing editing date if (step.getProcessingTime() == null) { isDirty = true; if (step.getProcessingEnd() == null) { step.setProcessingTime(step.getProcessingBegin()); } else { step.setProcessingTime(step.getProcessingEnd()); } } // fix missing end date if (step.getProcessingEnd() == null) { isDirty = true; step.setProcessingEnd(step.getProcessingTime()); } // attempts to add a history event, // exists method returns null if event already exists he = addHistoryEvent(step.getProcessingEnd(), step.getOrdering(), step.getTitle(), HistoryTypeEnum.taskDone, inProcess); if (he != null) { isDirty = true; } // for each step done we need to create a step open event on // that step based on // the latest timestamp for the previous step he = addHistoryEvent(getTimestampFromPreviousStep(inProcess, step), step.getOrdering(), step.getTitle(), HistoryTypeEnum.taskOpen, inProcess); if (he != null) { isDirty = true; } break; case INWORK: // fix missing start date if (step.getProcessingBegin() == null) { isDirty = true; if (step.getProcessingTime() == null) { step.setProcessingBegin(getTimestampFromPreviousStep(inProcess, step)); } else { step.setProcessingBegin(step.getProcessingTime()); } } // fix missing editing date if (step.getProcessingTime() == null) { isDirty = true; step.setProcessingTime(step.getProcessingBegin()); } // enc date must be null if (step.getProcessingEnd() != null) { step.setProcessingEnd(null); isDirty = true; } he = addHistoryEvent(step.getProcessingBegin(), step.getOrdering(), step.getTitle(), HistoryTypeEnum.taskInWork, inProcess); if (he != null) { isDirty = true; } // for each step inwork we need to create a step open event // on that step based on // the latest timestamp from the previous step he = addHistoryEvent(getTimestampFromPreviousStep(inProcess, step), step.getOrdering(), step.getTitle(), HistoryTypeEnum.taskOpen, inProcess); if (he != null) { isDirty = true; } break; case OPEN: // fix set start date - decision is that reopened (and // therfore with timestamp for begin) // shouldn't be reset /* * if (step.getBearbeitungsbeginn() != null) { * step.setBearbeitungsbeginn(null); isDirty = true; } */ // fix missing editing date if (step.getProcessingTime() == null) { isDirty = true; if (step.getProcessingEnd() != null) { step.setProcessingTime(step.getProcessingEnd()); } else { // step.setBearbeitungsbeginn(getTimestampFromPreviousStep(inProcess, // step)); step.setProcessingTime(getTimestampFromPreviousStep(inProcess, step)); } } // fix set end date if (step.getProcessingEnd() != null) { step.setProcessingEnd(null); isDirty = true; } he = addHistoryEvent(step.getProcessingTime(), step.getOrdering(), step.getTitle(), HistoryTypeEnum.taskOpen, inProcess); if (he != null) { isDirty = true; } break; } // check corrections timestamp this clearly only works on past // correction events done in the german // language current corrections directly adds to the history // adds for each step a step locked on the basis of the process // creation timestamp (new in 1.6) he = addHistoryEvent(inProcess.getCreationDate(), step.getOrdering(), step.getTitle(), HistoryTypeEnum.taskLocked, inProcess); if (he != null) { isDirty = true; } } // this method removes duplicate items from the history list, which // already happened to be there, isDirty will be automatically be set if (getHistoryEventDuplicated(inProcess)) { isDirty = true; } return isDirty; } /** * Add HistoryEvent. * * @param timeStamp * Date * @param stepOrder * Integer * @param stepName * String * @param type * HistoryTypeEnum object * @param inProcess * Process object * @return History event if event needs to be added, null if event(same * kind, same time, same process) already exists */ private static History addHistoryEvent(Date timeStamp, Integer stepOrder, String stepName, HistoryTypeEnum type, Process inProcess) { History he = new History(timeStamp, stepOrder, stepName, type, inProcess); if (!getHistoryContainsEventAlready(he, inProcess)) { serviceManager.getProcessService().getHistoryInitialized(inProcess).add(he); return he; } else { return null; } } /** * check if history already contains given event. * * @param inEvent * given {@link History} * @param inProcess * given {@link Process} * @return true, if {@link History} already exists */ private static Boolean getHistoryContainsEventAlready(History inEvent, Process inProcess) { for (History historyItem : inProcess.getHistory()) { if (inEvent != historyItem) { // this is required, in case items // from the same list are compared if (historyItem.equals(inEvent)) { return true; } } } return false; } /** * get stored value (all diffs as sum) from history. * * @return stored value as Long */ private static Long getStoredValue(Process inProcess, HistoryTypeEnum inType) { long storedValue = 0; for (History historyItem : inProcess.getHistory()) { if (historyItem.getHistoryType() == inType) { storedValue += historyItem.getNumericValue().longValue(); } } return storedValue; } /** * update history, if current value is different to stored value. * * @return true if value is different and history got updated, else false */ private static Boolean updateHistoryEvent(Process inProcess, HistoryTypeEnum inType, Long inCurrentValue) throws DataException { long storedValue = getStoredValue(inProcess, inType); long diff = inCurrentValue - storedValue; // if storedValue is different to current value - update history if (diff != 0) { History history = new History(new Date(), diff, null, inType, inProcess); serviceManager.getHistoryService().save(history); return true; } else { return false; } } /** * Size of Storage in Bytes per {@link Process}. * * @return size in bytes, or 0 if error. */ private static long getCurrentStorageSize(Process inProcess) throws IOException { URI dir = serviceManager.getProcessService().getProcessDataDirectory(inProcess); File directory = new File(dir); if (!serviceManager.getFileService().isDirectory(dir)) { throw new IOException("History Manager error while calculating size of " + inProcess.getTitle()); } return org.apache.commons.io.FileUtils.sizeOfDirectory(directory); } /** * updateHistoryForAllProcesses. */ public void updateHistoryForAllProcesses() { logger.info("start history updating for all processes"); try { Session session = Helper.getHibernateSession(); Query query = session.createQuery("from Process order by id desc"); @SuppressWarnings("unchecked") Iterator<Process> it = query.iterate(); int i = 0; while (it.hasNext()) { i++; Process proc = it.next(); if (logger.isDebugEnabled()) { logger.debug("updating history entries for " + proc.getTitle()); } try { // commit transaction every 50 items if (!it.hasNext() || i % 50 == 0) { session.flush(); session.beginTransaction().commit(); session.clear(); } } catch (HibernateException e) { logger.error("HibernateException occurred while scheduled storage calculation", e); } catch (Exception e) { Helper.setFehlerMeldung("An error occurred while scheduled storage calculation", e); logger.error("ServletException occurred while scheduled storage calculation", e); } } } catch (Exception e) { Helper.setFehlerMeldung("Another Exception occurred while scheduled storage calculation", e); logger.error("Another Exception occurred while scheduled storage calculation", e); } logger.info("end history updating for all processes"); } /** * method returns a timestamp from a previous step, iterates through the * steps if necessary. * * @param inProcess * Process object * @param inStep * Task object */ private static Date getTimestampFromPreviousStep(Process inProcess, Task inStep) { Date eventTimestamp = null; List<Task> tempList = inProcess.getTasks(); for (Task s : tempList) { // making sure that we only look for timestamps in the step below // this one int index = tempList.indexOf(s); if (s == inStep && index != 0) { Task prevStep = tempList.get(index - 1); if (prevStep.getProcessingEnd() != null) { return prevStep.getProcessingEnd(); } if (prevStep.getProcessingTime() != null) { return prevStep.getProcessingTime(); } if (prevStep.getProcessingBegin() != null) { return prevStep.getProcessingBegin(); } eventTimestamp = getTimestampFromPreviousStep(inProcess, prevStep); } } if (eventTimestamp == null) { if (inProcess.getCreationDate() != null) { eventTimestamp = inProcess.getCreationDate(); } else { // if everything fails we use the current date Calendar cal = Calendar.getInstance(); cal.set(2007, 0, 1, 0, 0, 0); eventTimestamp = cal.getTime(); if (logger.isInfoEnabled()) { logger.info("We had to use 2007-1-1 date '" + eventTimestamp.toString() + "' for a history event as a fallback"); } } } return eventTimestamp; } /** * Method iterates through the event list and checks if there are duplicate * entries, if so it will remove the entry and return a true. * * @param inProcess * Process object * @return true if there are duplicate entries, false otherwise */ private static Boolean getHistoryEventDuplicated(Process inProcess) { Boolean duplicateEventRemoved = false; for (History he : inProcess.getHistory()) { if (getHistoryContainsEventAlready(he, inProcess)) { inProcess.getHistory().remove(he); duplicateEventRemoved = true; } } return duplicateEventRemoved; } /** * Update history for process. * * @param inProc * Process object * @return Boolean */ public static Boolean updateHistoryForProcess(Process inProc) { Boolean updated = false; try { updated = updateHistory(inProc); updated = updateHistoryForSteps(inProc); } catch (Exception ex) { logger.warn("Updating history failed.", ex); updated = false; } return updated; } }