blob: 446ddd7bda4e3fc1d3b8be3a6c2232ad335b9c77 [file] [log] [blame]
/*
* Copyright 1999-2004 The Apache Software Foundation.
*
* 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.apache.cocoon.generation;
import java.io.IOException;
import java.io.Serializable;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.cocoon.ProcessingException;
import org.apache.cocoon.caching.CacheableProcessingComponent;
import org.apache.cocoon.environment.SourceResolver;
import org.apache.commons.lang.BooleanUtils;
import org.apache.excalibur.source.SourceValidity;
import org.apache.excalibur.source.impl.validity.NOPValidity;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
/**
* @cocoon.sitemap.component.documentation
* Generates an XML document representing a calendar for a given month and year.
*
* @cocoon.sitemap.component.documentation.caching TBD
* @cocoon.sitemap.component.name calendar
* @cocoon.sitemap.component.label content
* @cocoon.sitemap.component.logger sitemap.generator.calendar
*
*
* <p>
* Here is a sample output:
* </p>
* <pre>
* &lt;calendar:calendar xmlns:calendar="http://apache.org/cocoon/calendar/1.0"
* year="2004" month="January" prevMonth="12" prevYear="2003"
* nextMonth="02" nextYear="2004"&gt;
* &lt;calendar:week number="1"&gt;
* &lt;calendar:day number="1" weekday="THURSDAY" date="January 1, 2004"/&gt;
* &lt;calendar:day number="2" weekday="FRIDAY" date="January 2, 2004"/&gt;
* &lt;calendar:day number="3" weekday="SATURDAY" date="January 3, 2004"/&gt;
* &lt;calendar:day number="4" weekday="SUNDAY" date="January 4, 2004"/&gt;
* &lt;/calendar:week&gt;
* ...
* &lt;/calendar:calendar&gt;
* </pre>
* <p>
* The <i>src</i> parameter is ignored.
* </p>
* <p>
* <b>Configuration options:</b>
* <dl>
* <dt> <i>month</i> (optional)</dt>
* <dd> Sets the month for the calendar (January is 1). Default is the current month.</dd>
* <dt> <i>year</i> (optional)</dt>
* <dd> Sets the year for the calendar. Default is the current year.</dd>
* <dt> <i>dateFormat</i> (optional)</dt>
* <dd> Sets the format for the date attribute of each node, as
* described in java.text.SimpleDateFormat. If unset, the default
* format for the current locale will be used.</dd>
* <dt> <i>lang</i> (optional)</dt>
* <dd> Sets the ISO language code for determining the locale.</dd>
* <dt> <i>country</i> (optional)</dt>
* <dd> Sets the ISO country code for determining the locale.</dd>
* <dt> <i>padWeeks</i> (optional)</dt>
* <dd> If set to true, full weeks will be generated by adding
* days from the end of the previous month and the beginning
* of the following month.</dd>
* </dl>
* </p>
*
* @version $Id$
*/
public class CalendarGenerator extends ServiceableGenerator implements CacheableProcessingComponent {
/** The URI of the namespace of this generator. */
protected static final String URI = "http://apache.org/cocoon/calendar/1.0";
/** The namespace prefix for this namespace. */
protected static final String PREFIX = "calendar";
/** Node and attribute names */
protected static final String CALENDAR_NODE_NAME = "calendar";
protected static final String WEEK_NODE_NAME = "week";
protected static final String DAY_NODE_NAME = "day";
protected static final String MONTH_ATTR_NAME = "month";
protected static final String YEAR_ATTR_NAME = "year";
protected static final String DATE_ATTR_NAME = "date";
protected static final String NUMBER_ATTR_NAME = "number";
protected static final String WEEKDAY_ATTR_NAME = "weekday";
protected static final String PREV_MONTH_ATTR_NAME = "prevMonth";
protected static final String PREV_YEAR_ATTR_NAME = "prevYear";
protected static final String NEXT_MONTH_ATTR_NAME = "nextMonth";
protected static final String NEXT_YEAR_ATTR_NAME = "nextYear";
/** Formatter for month number */
protected static final DecimalFormat monthNumberFormatter = new DecimalFormat("00");
/** Convenience object, so we don't need to create an AttributesImpl for every element. */
protected AttributesImpl attributes;
/**
* The cache key needs to be generated for the configuration of this
* generator, so storing the parameters for generateKey().
*/
protected List cacheKeyParList;
/** The year to generate the calendar for */
protected int year;
/** The month to generate the calendar for */
protected int month;
/** The format for dates */
protected DateFormat dateFormatter;
/** The format for month names */
protected DateFormat monthFormatter;
/** The current locale */
protected Locale locale;
/** Do we need to pad out the first and last weeks? */
protected boolean padWeeks;
/* Add the day of the week
*
* since SUNDAY=1, we start with a dummy
* entry.
*/
protected String weekdays[] = { "",
"SUNDAY", "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY",
"FRIDAY", "SATURDAY"
};
/**
* Set the request parameters. Must be called before the generate method.
*
* @param resolver the SourceResolver object
* @param objectModel a <code>Map</code> containing model object
* @param src the source URI (ignored)
* @param par configuration parameters
*/
public void setup(SourceResolver resolver, Map objectModel, String src, Parameters par)
throws ProcessingException, SAXException, IOException {
super.setup(resolver, objectModel, src, par);
this.cacheKeyParList = new ArrayList();
this.cacheKeyParList.add(src);
// Determine the locale
String langString = par.getParameter("lang", null);
locale = Locale.getDefault();
if (langString != null) {
this.cacheKeyParList.add(langString);
String countryString = par.getParameter("country", "");
if (! "".equals(countryString)) {
this.cacheKeyParList.add(countryString);
}
locale = new Locale(langString, countryString);
}
// Determine year and month. Default is current year and month.
Calendar now = Calendar.getInstance(locale);
this.year = par.getParameterAsInteger("year", now.get(Calendar.YEAR));
this.cacheKeyParList.add(String.valueOf(this.year));
this.month = par.getParameterAsInteger("month", now.get(Calendar.MONTH) + 1) - 1;
this.cacheKeyParList.add(String.valueOf(this.month));
String dateFormatString = par.getParameter("dateFormat", null);
this.cacheKeyParList.add(dateFormatString);
if (dateFormatString != null) {
this.dateFormatter = new SimpleDateFormat(dateFormatString, locale);
} else {
this.dateFormatter = DateFormat.getDateInstance(DateFormat.LONG, locale);
}
this.padWeeks = par.getParameterAsBoolean("padWeeks", false);
this.cacheKeyParList.add(BooleanUtils.toBooleanObject(this.padWeeks));
this.monthFormatter = new SimpleDateFormat("MMMM", locale);
this.attributes = new AttributesImpl();
}
/**
* Generate XML data.
*
* @throws SAXException if an error occurs while outputting the document
*/
public void generate() throws SAXException, ProcessingException {
Calendar start = Calendar.getInstance(locale);
start.clear();
start.set(Calendar.YEAR, this.year);
start.set(Calendar.MONTH, this.month);
start.set(Calendar.DAY_OF_MONTH, 1);
Calendar end = (Calendar) start.clone();
end.add(Calendar.MONTH, 1);
// Determine previous and next months
Calendar prevMonth = (Calendar) start.clone();
prevMonth.add(Calendar.MONTH, -1);
this.contentHandler.startDocument();
this.contentHandler.startPrefixMapping(PREFIX, URI);
attributes.clear();
attributes.addAttribute("", YEAR_ATTR_NAME, YEAR_ATTR_NAME, "CDATA", String.valueOf(year));
attributes.addAttribute("", MONTH_ATTR_NAME, MONTH_ATTR_NAME, "CDATA",
monthFormatter.format(start.getTime()));
// Add previous and next month
attributes.addAttribute("", PREV_YEAR_ATTR_NAME, PREV_YEAR_ATTR_NAME, "CDATA",
String.valueOf(prevMonth.get(Calendar.YEAR)));
attributes.addAttribute("", PREV_MONTH_ATTR_NAME, PREV_MONTH_ATTR_NAME, "CDATA",
monthNumberFormatter.format(prevMonth.get(Calendar.MONTH) + 1));
attributes.addAttribute("", NEXT_YEAR_ATTR_NAME, NEXT_YEAR_ATTR_NAME, "CDATA",
String.valueOf(end.get(Calendar.YEAR)));
attributes.addAttribute("", NEXT_MONTH_ATTR_NAME, NEXT_MONTH_ATTR_NAME, "CDATA",
monthNumberFormatter.format(end.get(Calendar.MONTH) + 1));
this.contentHandler.startElement(URI, CALENDAR_NODE_NAME,
PREFIX + ':' + CALENDAR_NODE_NAME, attributes);
int weekNo = start.get(Calendar.WEEK_OF_MONTH);
int firstDay = start.getFirstDayOfWeek();
if (start.get(Calendar.DAY_OF_WEEK) != firstDay) {
attributes.clear();
attributes.addAttribute("", NUMBER_ATTR_NAME, NUMBER_ATTR_NAME, "CDATA", String.valueOf(weekNo));
this.contentHandler.startElement(URI, WEEK_NODE_NAME,
PREFIX + ':' + WEEK_NODE_NAME, attributes);
if (padWeeks) {
Calendar previous = (Calendar) start.clone();
while (previous.get(Calendar.DAY_OF_WEEK) != firstDay) {
previous.add(Calendar.DAY_OF_MONTH, -1);
}
while (previous.before(start)) {
attributes.clear();
attributes.addAttribute("", NUMBER_ATTR_NAME, NUMBER_ATTR_NAME, "CDATA",
String.valueOf(previous.get(Calendar.DAY_OF_MONTH)));
attributes.addAttribute("", WEEKDAY_ATTR_NAME, WEEKDAY_ATTR_NAME, "CDATA",
weekdays[previous.get(Calendar.DAY_OF_WEEK)]);
attributes.addAttribute("", DATE_ATTR_NAME, DATE_ATTR_NAME, "CDATA",
dateFormatter.format(previous.getTime()));
this.contentHandler.startElement(URI, DAY_NODE_NAME,
PREFIX + ':' + DAY_NODE_NAME, attributes);
addContent(previous, locale);
this.contentHandler.endElement(URI, DAY_NODE_NAME,
PREFIX + ':' + DAY_NODE_NAME);
previous.add(Calendar.DAY_OF_MONTH, 1);
}
}
}
while (start.before(end)) {
if (start.get(Calendar.DAY_OF_WEEK) == firstDay) {
weekNo = start.get(Calendar.WEEK_OF_MONTH);
attributes.clear();
attributes.addAttribute("", NUMBER_ATTR_NAME, NUMBER_ATTR_NAME, "CDATA", String.valueOf(weekNo));
this.contentHandler.startElement(URI, WEEK_NODE_NAME,
PREFIX + ':' + WEEK_NODE_NAME, attributes);
}
attributes.clear();
attributes.addAttribute("", NUMBER_ATTR_NAME, NUMBER_ATTR_NAME, "CDATA",
String.valueOf(start.get(Calendar.DAY_OF_MONTH)));
attributes.addAttribute("", WEEKDAY_ATTR_NAME, WEEKDAY_ATTR_NAME, "CDATA",
weekdays[start.get(Calendar.DAY_OF_WEEK)]);
attributes.addAttribute("", DATE_ATTR_NAME, DATE_ATTR_NAME, "CDATA",
dateFormatter.format(start.getTime()));
this.contentHandler.startElement(URI, DAY_NODE_NAME,
PREFIX + ':' + DAY_NODE_NAME, attributes);
addContent(start, locale);
this.contentHandler.endElement(URI, DAY_NODE_NAME,
PREFIX + ':' + DAY_NODE_NAME);
start.add(Calendar.DAY_OF_MONTH, 1);
if (start.get(Calendar.DAY_OF_WEEK) == firstDay
|| (!padWeeks && ! start.before(end))) {
this.contentHandler.endElement(URI, WEEK_NODE_NAME,
PREFIX + ':' + WEEK_NODE_NAME);
}
}
if (padWeeks) {
while (firstDay != end.get(Calendar.DAY_OF_WEEK)) {
attributes.clear();
attributes.addAttribute("", NUMBER_ATTR_NAME, NUMBER_ATTR_NAME, "CDATA",
String.valueOf(end.get(Calendar.DAY_OF_MONTH)));
attributes.addAttribute("", WEEKDAY_ATTR_NAME, WEEKDAY_ATTR_NAME, "CDATA",
weekdays[end.get(Calendar.DAY_OF_WEEK)]);
attributes.addAttribute("", DATE_ATTR_NAME, DATE_ATTR_NAME, "CDATA",
dateFormatter.format(end.getTime()));
this.contentHandler.startElement(URI, DAY_NODE_NAME,
PREFIX + ':' + DAY_NODE_NAME, attributes);
addContent(end, locale);
this.contentHandler.endElement(URI, DAY_NODE_NAME,
PREFIX + ':' + DAY_NODE_NAME);
end.add(Calendar.DAY_OF_MONTH, 1);
if (firstDay == end.get(Calendar.DAY_OF_WEEK)) {
this.contentHandler.endElement(URI, WEEK_NODE_NAME,
PREFIX + ':' + WEEK_NODE_NAME);
}
}
}
this.contentHandler.endElement(URI, CALENDAR_NODE_NAME,
PREFIX + ':' + CALENDAR_NODE_NAME);
this.contentHandler.endPrefixMapping(PREFIX);
this.contentHandler.endDocument();
}
/**
* Add content to a &lt;day&gt; element. This method is intended to be overridden
* by subclasses that want to add content to one or more days of the calendar.
*
* @param date The date corresponding to the current element.
* @param locale The current locale.
* @throws SAXException if an error occurs while outputting the document
*/
protected void addContent(Calendar date, Locale locale) throws SAXException {}
/* (non-Javadoc)
* @see org.apache.cocoon.caching.CacheableProcessingComponent#getKey()
*/
public Serializable getKey() {
StringBuffer buffer = new StringBuffer();
int len = this.cacheKeyParList.size();
for (int i = 0; i < len; i++) {
buffer.append(this.cacheKeyParList.get(i) + ":");
}
return buffer.toString();
}
/* (non-Javadoc)
* @see org.apache.cocoon.caching.CacheableProcessingComponent#getValidity()
*/
public SourceValidity getValidity() {
return NOPValidity.SHARED_INSTANCE;
}
/**
* Recycle resources
* @see org.apache.avalon.excalibur.pool.Recyclable#recycle()
*/
public void recycle() {
this.cacheKeyParList = null;
this.attributes = null;
this.dateFormatter = null;
this.monthFormatter = null;
this.locale = null;
super.recycle();
}
}