/* * File : $Source: /alkacon/cvs/alkacon/com.alkacon.opencms.v8.calendar/src/com/alkacon/opencms/v8/calendar/CmsSerialDateXmlContentHandler.java,v $ * Date : $Date: 2009/02/05 09:49:31 $ * Version: $Revision: 1.2 $ * * This file is part of the Alkacon OpenCms Add-On Module Package * * Copyright (c) 2008 Alkacon Software GmbH (http://www.alkacon.com) * * The Alkacon OpenCms Add-On Module Package is free software: * you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The Alkacon OpenCms Add-On Module Package 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. * * You should have received a copy of the GNU General Public License * along with the Alkacon OpenCms Add-On Module Package. * If not, see http://www.gnu.org/licenses/. * * For further information about Alkacon Software GmbH, please see the * company website: http://www.alkacon.com. * * For further information about OpenCms, please see the * project website: http://www.opencms.org. */ package com.alkacon.opencms.v8.calendar; import org.opencms.file.CmsObject; import org.opencms.file.CmsProperty; import org.opencms.file.CmsResource; import org.opencms.file.CmsResourceFilter; import org.opencms.main.CmsException; import org.opencms.main.OpenCms; import org.opencms.util.CmsStringUtil; import org.opencms.widgets.CmsCalendarWidget; import org.opencms.workplace.CmsWorkplaceMessages; import org.opencms.xml.CmsXmlContentDefinition; import org.opencms.xml.CmsXmlException; import org.opencms.xml.content.CmsDefaultXmlContentHandler; import org.opencms.xml.content.CmsXmlContent; import org.opencms.xml.content.CmsXmlContentErrorHandler; import org.opencms.xml.content.Messages; import org.opencms.xml.types.CmsXmlNestedContentDefinition; import org.opencms.xml.types.I_CmsXmlContentValue; import org.opencms.xml.types.I_CmsXmlSchemaType; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; /** * Special XML content handler that validates serial date series changes and * writes changed occurences to a configurable property value.<p> * * @author Andreas Zahner * */ public class CmsSerialDateXmlContentHandler extends CmsDefaultXmlContentHandler { /** The node name for the Change node. */ public static final String NODE_CHANGE = "Change"; /** The node name for the End node. */ public static final String NODE_END = "End"; /** The node name for the Interruption node. */ public static final String NODE_INTERRUPTION = "Interruption"; /** The node name for the Serialdate node. */ public static final String NODE_SERIALDATE = "Serialdate"; /** The node name for the Start node. */ public static final String NODE_START = "Start"; /** Name of the property to write the serial date change information to. */ public static final String PROPERTY_SERIALDATE_CHANGE = "calendar.dateserial.chg"; /** Name of the property to write the serial date interruption information to. */ public static final String PROPERTY_SERIALDATE_INTERRUPTION = "calendar.dateserial.int"; /** The flag indicating that a series entry is changed. */ public static final String SERIES_FLAG_CHANGED = "chg"; /** The flag indicating that a series entry is removed. */ public static final String SERIES_FLAG_REMOVED = "rem"; /** The xpath for the first Change sub node. */ public static final String XPATH_CHANGE = NODE_CHANGE + "[1]/" + NODE_CHANGE + "[1]"; /** The xpath for the first Change sub node. */ public static final String XPATH_INTERRUPTION = NODE_INTERRUPTION + "[1]/" + NODE_START + "[1]"; /** * Empty constructor.<p> */ public CmsSerialDateXmlContentHandler() { super(); } /** * @see org.opencms.xml.content.I_CmsXmlContentHandler#resolveMapping(org.opencms.file.CmsObject, org.opencms.xml.content.CmsXmlContent, org.opencms.xml.types.I_CmsXmlContentValue) */ public void resolveMapping(CmsObject cms, CmsXmlContent content, I_CmsXmlContentValue value) throws CmsException { // first resolve usual mapping by calling super implementation super.resolveMapping(cms, content, value); // get locale of value to check Locale locale = value.getLocale(); // check if the serial date change property has to be deleted boolean deletePropertyValue = false; if (value.getName().equals(NODE_SERIALDATE)) { if (!content.hasValue(XPATH_CHANGE, locale)) { deletePropertyValue = true; } } if (deletePropertyValue || value.getPath().equals(XPATH_CHANGE)) { // get the original VFS file from the content to check if it is present if (content.getFile() == null) { throw new CmsXmlException(Messages.get().container(Messages.ERR_XMLCONTENT_RESOLVE_FILE_NOT_FOUND_0)); } // create OpenCms user context initialized with "/" as site root to read all siblings CmsObject rootCms = OpenCms.initCmsObject(cms); rootCms.getRequestContext().setSiteRoot("/"); // read all siblings of the file List siblings = rootCms.readSiblings(content.getFile().getRootPath(), CmsResourceFilter.IGNORE_EXPIRATION); // for multiple language mappings, we need to ensure // a) all siblings are handled // b) only the "right" locale is mapped to a sibling for (int i = (siblings.size() - 1); i >= 0; i--) { // get filename String filename = ((CmsResource)siblings.get(i)).getRootPath(); Locale fileLocale = OpenCms.getLocaleManager().getDefaultLocale(rootCms, filename); if (!fileLocale.equals(value.getLocale())) { // only map property if the locale fits continue; } // set the property value String propValue = CmsProperty.DELETE_VALUE; if (!deletePropertyValue) { // collect information about serial date changes StringBuffer collectedChanges = new StringBuffer(256); // get the type sequence for the nested change definitions I_CmsXmlSchemaType type = content.getContentDefinition().getSchemaType(NODE_CHANGE); CmsXmlNestedContentDefinition nestedSchema = (CmsXmlNestedContentDefinition)type; CmsXmlContentDefinition nestedDefinition = nestedSchema.getNestedContentDefinition(); List typeSequence = nestedDefinition.getTypeSequence(); // get the change values to check List changeValues = content.getValues(NODE_CHANGE, locale); int changesSize = changeValues.size(); boolean validChangeFound = false; // set the list of serial date entries List possibleEntries = new ArrayList(); if (changesSize > 0) { possibleEntries = getPossibleEntries(cms, content, locale); } for (int k = 0; k < changesSize; k++) { // loop the change values I_CmsXmlContentValue changeValue = (I_CmsXmlContentValue)changeValues.get(k); String xPath = changeValue.getPath() + "/"; // get the value for the change String changeIndex = content.getStringValue(cms, xPath + NODE_CHANGE, locale); if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(changeIndex)) { // we have a valid change defined, now check if there are any values active overwriting entry data Iterator it = typeSequence.iterator(); String flag = SERIES_FLAG_REMOVED; while (it.hasNext()) { I_CmsXmlSchemaType nestedType = (I_CmsXmlSchemaType)it.next(); if (!nestedType.getName().equals(NODE_CHANGE) && content.hasValue(xPath + nestedType.getName(), locale)) { // found a field overwriting the series value flag = SERIES_FLAG_CHANGED; break; } } if (validChangeFound) { // appent delimiter to property value collectedChanges.append(CmsProperty.VALUE_LIST_DELIMITER); } // determine serial date that is changed CmsCalendarEntry entry = (CmsCalendarEntry)possibleEntries.get(Integer.parseInt(changeIndex) - 1); if (entry != null) { // write changed date and flag to property value long time = entry.getEntryDate().getStartDate().getTimeInMillis(); if (content.hasValue(xPath + "Time", locale)) { try { CmsWorkplaceMessages messages = OpenCms.getWorkplaceManager().getMessages( locale); String dateStr = CmsSerialDateWidget.getCalendarLocalizedTime( locale, messages, entry.getEntryDate().getStartDate().getTimeInMillis(), true, false); time = CmsCalendarWidget.getCalendarDate( messages, dateStr + " " + content.getStringValue(cms, xPath + "Time", locale), true); } catch (Exception e) { // ignore, changed time will not be considered } } String dateValue = String.valueOf(time); collectedChanges.append(dateValue); collectedChanges.append(CmsProperty.VALUE_MAP_DELIMITER); collectedChanges.append(flag); validChangeFound = true; } } } propValue = collectedChanges.toString(); } // map to individual value CmsProperty p = new CmsProperty(PROPERTY_SERIALDATE_CHANGE, propValue, null, true); // just store the string value in the selected property rootCms.writePropertyObject(filename, p); } } // check if the serial date interruption property has to be deleted deletePropertyValue = false; if (value.getName().equals(NODE_SERIALDATE)) { if (!content.hasValue(XPATH_INTERRUPTION, locale)) { deletePropertyValue = true; } } if (deletePropertyValue || value.getPath().equals(XPATH_INTERRUPTION)) { // get the original VFS file from the content to check if it is present if (content.getFile() == null) { throw new CmsXmlException(Messages.get().container(Messages.ERR_XMLCONTENT_RESOLVE_FILE_NOT_FOUND_0)); } // create OpenCms user context initialized with "/" as site root to read all siblings CmsObject rootCms = OpenCms.initCmsObject(cms); rootCms.getRequestContext().setSiteRoot("/"); // read all siblings of the file List siblings = rootCms.readSiblings(content.getFile().getRootPath(), CmsResourceFilter.IGNORE_EXPIRATION); // for multiple language mappings, we need to ensure // a) all siblings are handled // b) only the "right" locale is mapped to a sibling for (int i = (siblings.size() - 1); i >= 0; i--) { // get filename String filename = ((CmsResource)siblings.get(i)).getRootPath(); Locale fileLocale = OpenCms.getLocaleManager().getDefaultLocale(rootCms, filename); if (!fileLocale.equals(value.getLocale())) { // only map property if the locale fits continue; } // set the property value String propValue = CmsProperty.DELETE_VALUE; if (!deletePropertyValue) { // collect information about serial date changes StringBuffer collectedInts = new StringBuffer(256); // get the interruption values to check List intValues = content.getValues(NODE_INTERRUPTION, locale); int intsSize = intValues.size(); boolean validIntFound = false; for (int k = 0; k < intsSize; k++) { // loop the interruption values I_CmsXmlContentValue intValue = (I_CmsXmlContentValue)intValues.get(k); String xPath = intValue.getPath() + "/"; // get the start date value for the interruption String startDateStr = content.getStringValue(cms, xPath + NODE_START, locale); // get the end date value for the interruption String endDateStr = content.getStringValue(cms, xPath + NODE_END, locale); if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(startDateStr) && CmsStringUtil.isNotEmptyOrWhitespaceOnly(endDateStr)) { // we have a valid interruption defined, append date values to property value if (validIntFound) { // append delimiter to property value collectedInts.append(CmsProperty.VALUE_LIST_DELIMITER); } collectedInts.append(startDateStr); collectedInts.append(CmsProperty.VALUE_MAP_DELIMITER); collectedInts.append(endDateStr); validIntFound = true; } } propValue = collectedInts.toString(); } // map to individual value CmsProperty p = new CmsProperty(PROPERTY_SERIALDATE_INTERRUPTION, propValue, null, true); // just store the string value in the selected property rootCms.writePropertyObject(filename, p); } } } /** * Returns the possible change entries for the configured serial date.<p> * * Be sure that at least one change is available in the content before calling this method.<p> * * @param cms the current OpenCms user context * @param content the XML content to use * @param locale the locale to get the values for * @return the possible change entries for the configured serial date */ protected List getPossibleEntries(CmsObject cms, CmsXmlContent content, Locale locale) { List possibleEntries = new ArrayList(); try { // get the serial date select widget, initialize it I_CmsXmlContentValue changeDateValue = content.getValue(XPATH_CHANGE, locale); CmsSerialDateSelectWidget widget = (CmsSerialDateSelectWidget)changeDateValue.getContentDefinition().getContentHandler().getWidget( changeDateValue); widget.initConfiguration(widget.getConfiguration()); Map serialDateValues = CmsStringUtil.splitAsMap( content.getStringValue(cms, NODE_SERIALDATE, locale), String.valueOf(CmsProperty.VALUE_LIST_DELIMITER), String.valueOf(CmsProperty.VALUE_MAP_DELIMITER)); // get the number of calendar entries possibleEntries = CmsSerialDateSelectWidget.getCalendarEntries( serialDateValues, locale, widget.getEntryCount()); } catch (CmsException e) { // error getting the entries } return possibleEntries; } /** * @see org.opencms.xml.content.CmsDefaultXmlContentHandler#validateValue(org.opencms.file.CmsObject, org.opencms.xml.types.I_CmsXmlContentValue, org.opencms.xml.content.CmsXmlContentErrorHandler, java.util.Map, boolean) */ protected CmsXmlContentErrorHandler validateValue( CmsObject cms, I_CmsXmlContentValue value, CmsXmlContentErrorHandler errorHandler, Map rules, boolean isWarning) { if (errorHandler == null) { // initialize error handler if not yet available errorHandler = new CmsXmlContentErrorHandler(); } if (value.getPath().equals(XPATH_CHANGE)) { // check the defined changes CmsXmlContent content = (CmsXmlContent)value.getDocument(); Locale locale = value.getLocale(); List changeValues = content.getValues(NODE_CHANGE, locale); int changesSize = changeValues.size(); List storedIndexes = new ArrayList(changesSize); // calculate the possible number of entries int possibleEntriesSize = -1; if (changesSize > 0) { possibleEntriesSize = getPossibleEntries(cms, content, locale).size(); } for (int k = 0; k < changesSize; k++) { // loop the change values I_CmsXmlContentValue changeValue = (I_CmsXmlContentValue)changeValues.get(k); String xPath = changeValue.getPath() + "/"; // get the value for the exception String changeIndex = content.getStringValue(cms, xPath + NODE_CHANGE, locale); I_CmsXmlContentValue changeDateValue = content.getValue(xPath + NODE_CHANGE, locale); if (CmsStringUtil.isEmptyOrWhitespaceOnly(changeIndex)) { // no date selected, show error errorHandler.addError(changeDateValue, key("GUI_VALIDATION_SERIALDATE_NOTSELECTED_0", locale)); } else if (Integer.parseInt(changeIndex) > possibleEntriesSize) { // entry index larger than number of possible entries, show error errorHandler.addError(changeDateValue, key("GUI_VALIDATION_SERIALDATE_ENTRYNOTFOUND_0", locale)); } else { if (storedIndexes.contains(changeIndex)) { // date was already selected earlier, show error errorHandler.addError(changeDateValue, key("GUI_VALIDATION_SERIALDATE_DEFINED_0", locale)); } else { // add selected date to list storedIndexes.add(changeIndex); } } } } else { // other values use the default validation super.validateValue(cms, value, errorHandler, rules, isWarning); } return errorHandler; } }