001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.generation;
018:
019: import java.io.IOException;
020: import java.util.ArrayList;
021: import java.util.Arrays;
022: import java.util.Comparator;
023: import java.util.Date;
024: import java.util.Map;
025:
026: import org.apache.avalon.framework.parameters.Parameters;
027: import org.apache.avalon.framework.service.ServiceSelector;
028: import org.apache.cocoon.ProcessingException;
029: import org.apache.cocoon.environment.ObjectModelHelper;
030: import org.apache.cocoon.environment.Request;
031: import org.apache.cocoon.environment.SourceResolver;
032: import org.apache.lenya.cms.publication.Document;
033: import org.apache.lenya.cms.publication.DocumentException;
034: import org.apache.lenya.cms.publication.DocumentFactory;
035: import org.apache.lenya.cms.publication.DocumentUtil;
036: import org.apache.lenya.cms.publication.Publication;
037: import org.apache.lenya.cms.publication.PublicationUtil;
038: import org.apache.lenya.cms.repository.RepositoryUtil;
039: import org.apache.lenya.cms.repository.Session;
040: import org.apache.lenya.cms.site.SiteManager;
041: import org.apache.lenya.xml.DocumentHelper;
042: import org.apache.xpath.XPathAPI;
043: import org.w3c.dom.Element;
044: import org.xml.sax.SAXException;
045: import org.xml.sax.helpers.AttributesImpl;
046:
047: /**
048: * BlogOverviewGenerator
049: *
050: * Builds an ordered tree from the blog entries in the
051: * repository, allows simple queries.
052: *
053: */
054: public class BlogOverviewGenerator extends ServiceableGenerator {
055:
056: /** The URI of the namespace of this generator. */
057: protected static final String URI = "http://apache.org/cocoon/blog/1.0";
058:
059: /** The namespace prefix for this namespace. */
060: protected static final String PREFIX = "blog";
061:
062: /** Node and attribute names */
063: protected static final String BLOG_NODE_NAME = "overview";
064:
065: protected static final String ENTRY_NODE_NAME = "entry";
066:
067: protected static final String PATH_ATTR_NAME = "path";
068:
069: protected static final String URL_ATTR_NAME = "url";
070:
071: protected static final String TITLE_ATTR_NAME = "title";
072:
073: protected static final String LASTMOD_ATTR_NAME = "lastModified";
074:
075: protected static final String STRUCT_ATTR_NAME = "structure";
076:
077: protected static final String YEAR_NODE_NAME = "year";
078:
079: protected static final String MONTH_NODE_NAME = "month";
080:
081: protected static final String DAY_NODE_NAME = "day";
082:
083: protected static final String ID_ATTR_NAME = "id";
084:
085: /**
086: * Convenience object, so we don't need to create an AttributesImpl for
087: * every element.
088: */
089: protected AttributesImpl attributes;
090:
091: /**
092: * The Lenya-Area where the generator should work on
093: */
094: protected String area;
095:
096: /**
097: * Request parameters
098: */
099: protected int year;
100: protected int month;
101: protected int day;
102:
103: protected String structure;
104:
105: /**
106: * The request
107: */
108: protected Request request;
109:
110: /**
111: * Set the request parameters. Must be called before the generate method.
112: *
113: * @param resolver
114: * the SourceResolver object
115: * @param objectModel
116: * a <code>Map</code> containing model object
117: * @param src
118: * the source URI (ignored)
119: * @param par
120: * configuration parameters
121: */
122: public void setup(SourceResolver resolver, Map objectModel,
123: String src, Parameters par) throws ProcessingException,
124: SAXException, IOException {
125:
126: super .setup(resolver, objectModel, src, par);
127:
128: request = ObjectModelHelper.getRequest(this .objectModel);
129:
130: String param = request.getParameter("year");
131: if (param != null)
132: year = Integer.parseInt(param);
133: else
134: year = 0;
135: param = request.getParameter("month");
136: if (param != null)
137: month = Integer.parseInt(param);
138: else
139: month = 0;
140: param = request.getParameter("day");
141: if (param != null)
142: day = Integer.parseInt(param);
143: else
144: day = 0;
145:
146: structure = request.getParameter("struct");
147: if (structure != null) {
148: year = month = day = 0;
149: }
150:
151: area = par.getParameter("area", null);
152: if (area == null)
153: throw new ProcessingException("no area specified");
154:
155: this .attributes = new AttributesImpl();
156: }
157:
158: /**
159: * Generate XML data.
160: *
161: * @throws SAXException
162: * if an error occurs while outputting the document
163: */
164: public void generate() throws SAXException, ProcessingException {
165:
166: this .contentHandler.startDocument();
167: this .contentHandler.startPrefixMapping(PREFIX, URI);
168: attributes.clear();
169:
170: if (structure != null) {
171: attributes.addAttribute("", STRUCT_ATTR_NAME,
172: STRUCT_ATTR_NAME, "CDATA", String
173: .valueOf(structure));
174: }
175: this .contentHandler.startElement(URI, BLOG_NODE_NAME, PREFIX
176: + ':' + BLOG_NODE_NAME, attributes);
177:
178: ServiceSelector selector = null;
179: SiteManager siteManager = null;
180: try {
181: Session session = RepositoryUtil.getSession(this .manager,
182: request);
183: DocumentFactory map = DocumentUtil.createDocumentFactory(
184: this .manager, session);
185: Publication publication = PublicationUtil.getPublication(
186: this .manager, request);
187:
188: selector = (ServiceSelector) this .manager
189: .lookup(SiteManager.ROLE + "Selector");
190: siteManager = (SiteManager) selector.select(publication
191: .getSiteManagerHint());
192:
193: Document[] docs = siteManager.getDocuments(map,
194: publication, area);
195: ArrayList filteredDocs = new ArrayList(1);
196: for (int i = 0; i < docs.length; i++) {
197: String path = docs[i].getPath();
198: if (path.startsWith("/entries/")) {
199: int eYear = 0;
200: int eMonth = 0;
201: int eDay = 0;
202: boolean add = false;
203: String[] patterns = path.split("/");
204: eYear = Integer.parseInt(patterns[2]);
205: eMonth = Integer.parseInt(patterns[3]);
206: eDay = Integer.parseInt(patterns[4]);
207: /* determine matching documents */
208: if (year > 0) {
209: if (year == eYear) {
210: if (month > 0) {
211: if (month == eMonth) {
212: if (day > 0) {
213: if (day == eDay) {
214: /* add */
215: add = true;
216: }
217: } else {
218: /* add */
219: add = true;
220: }
221: }
222: } else {
223: if (day > 0) {
224: if (day == eDay) {
225: /* add */
226: add = true;
227: }
228: } else {
229: /* add */
230: add = true;
231: }
232: }
233: }
234: } else if (month > 0l) {
235: if (month == eMonth) {
236: if (day > 0) {
237: if (day == eDay) {
238: /* add */
239: add = true;
240: }
241: } else {
242: /* add */
243: add = true;
244: }
245: }
246: } else {
247: if (day > 0) {
248: if (day == eDay) {
249: /* add */
250: add = true;
251: }
252: } else {
253: /* add */
254: add = true;
255: }
256: }
257: if (add) {
258: filteredDocs.add((Object) docs[i]);
259: }
260: }
261: }
262:
263: /* sort entries by year -> month -> day -> lastModified */
264: Object[] sortedList = filteredDocs.toArray();
265: Arrays.sort(sortedList, new Comparator() {
266: public int compare(Object o1, Object o2) {
267: Document d1, d2;
268: int year1, month1, day1;
269: int year2, month2, day2;
270:
271: d1 = (Document) o1;
272: d2 = (Document) o2;
273:
274: String[] patterns;
275: try {
276: patterns = d1.getPath().split("/");
277: } catch (DocumentException e) {
278: throw new RuntimeException(e);
279: }
280: year1 = Integer.parseInt(patterns[2]);
281: month1 = Integer.parseInt(patterns[3]);
282: day1 = Integer.parseInt(patterns[4]);
283:
284: try {
285: patterns = d2.getPath().split("/");
286: } catch (DocumentException e) {
287: throw new RuntimeException(e);
288: }
289: year2 = Integer.parseInt(patterns[2]);
290: month2 = Integer.parseInt(patterns[3]);
291: day2 = Integer.parseInt(patterns[4]);
292:
293: if (year1 > year2) {
294: return 1;
295: } else if (year1 == year2) {
296: if (month1 > month2) {
297: return 1;
298: } else if (month1 == month2) {
299: if (day1 > day2) {
300: return 1;
301: } else if (day1 == day2) {
302: /* newest first */
303: try {
304: Date date1 = new Date(d1
305: .getLastModified());
306: Date date2 = new Date(d2
307: .getLastModified());
308: return date2.compareTo(date1);
309: } catch (DocumentException e) {
310: throw new RuntimeException(e);
311: }
312: } else {
313: return -1;
314: }
315: } else {
316: return -1;
317: }
318: } else {
319: return -1;
320: }
321: }
322: });
323:
324: /* group entries by year -> month -> day */
325: /* works because the list is sorted =) */
326: int currentYear = 0;
327: int currentMonth = 0;
328: int currentDay = 0;
329: boolean yearOpen = false;
330: boolean monthOpen = false;
331: boolean dayOpen = false;
332: for (int i = 0; i < sortedList.length; i++) {
333: Document doc = ((Document) sortedList[i]);
334: String[] patterns = doc.getPath().split("/");
335: int year = Integer.parseInt(patterns[2]);
336: int month = Integer.parseInt(patterns[3]);
337: int day = Integer.parseInt(patterns[4]);
338: if (year != currentYear) {
339: if (dayOpen) {
340: dayOpen = false;
341: this .contentHandler.endElement(URI,
342: DAY_NODE_NAME, PREFIX + ':'
343: + DAY_NODE_NAME);
344: }
345: if (monthOpen) {
346: monthOpen = false;
347: this .contentHandler.endElement(URI,
348: MONTH_NODE_NAME, PREFIX + ':'
349: + MONTH_NODE_NAME);
350: }
351: if (yearOpen) {
352: this .contentHandler.endElement(URI,
353: YEAR_NODE_NAME, PREFIX + ':'
354: + YEAR_NODE_NAME);
355: }
356: this .attributes.clear();
357: attributes
358: .addAttribute("", ID_ATTR_NAME,
359: ID_ATTR_NAME, "CDATA", String
360: .valueOf(year));
361: this .contentHandler.startElement(URI,
362: YEAR_NODE_NAME, PREFIX + ':'
363: + YEAR_NODE_NAME, attributes);
364: yearOpen = true;
365: currentYear = year;
366: currentMonth = 0;
367: currentDay = 0;
368: }
369: if (month != currentMonth) {
370: if (dayOpen) {
371: dayOpen = false;
372: this .contentHandler.endElement(URI,
373: DAY_NODE_NAME, PREFIX + ':'
374: + DAY_NODE_NAME);
375: }
376: if (monthOpen) {
377: this .contentHandler.endElement(URI,
378: MONTH_NODE_NAME, PREFIX + ':'
379: + MONTH_NODE_NAME);
380: }
381: this .attributes.clear();
382: attributes.addAttribute("", ID_ATTR_NAME,
383: ID_ATTR_NAME, "CDATA", String
384: .valueOf(month));
385: this .contentHandler.startElement(URI,
386: MONTH_NODE_NAME, PREFIX + ':'
387: + MONTH_NODE_NAME, attributes);
388: monthOpen = true;
389: currentMonth = month;
390: currentDay = 0;
391: }
392: if (day != currentDay) {
393: if (dayOpen) {
394: this .contentHandler.endElement(URI,
395: DAY_NODE_NAME, PREFIX + ':'
396: + DAY_NODE_NAME);
397: }
398: this .attributes.clear();
399: attributes.addAttribute("", ID_ATTR_NAME,
400: ID_ATTR_NAME, "CDATA", String.valueOf(day));
401: this .contentHandler.startElement(URI,
402: DAY_NODE_NAME,
403: PREFIX + ':' + DAY_NODE_NAME, attributes);
404: dayOpen = true;
405: currentDay = day;
406: }
407: if (structure == null) {
408: attributes.clear();
409: attributes.addAttribute("", PATH_ATTR_NAME,
410: PATH_ATTR_NAME, "CDATA", doc.getPath());
411: attributes.addAttribute("", URL_ATTR_NAME,
412: URL_ATTR_NAME, "CDATA", doc
413: .getCanonicalWebappURL());
414: org.w3c.dom.Document docDOM = DocumentHelper
415: .readDocument(doc.getInputStream());
416: Element parent = docDOM.getDocumentElement();
417: Element element = (Element) XPathAPI
418: .selectSingleNode(parent,
419: "/*[local-name() = 'entry']/*[local-name() = 'title']");
420: attributes.addAttribute("", TITLE_ATTR_NAME,
421: TITLE_ATTR_NAME, "CDATA", DocumentHelper
422: .getSimpleElementText(element));
423: attributes.addAttribute("", LASTMOD_ATTR_NAME,
424: LASTMOD_ATTR_NAME, "CDATA", String
425: .valueOf(doc.getLastModified()));
426: DocumentHelper.getSimpleElementText(element);
427: this .contentHandler.startElement(URI,
428: ENTRY_NODE_NAME, PREFIX + ':'
429: + ENTRY_NODE_NAME, attributes);
430: this .contentHandler.endElement(URI,
431: ENTRY_NODE_NAME, PREFIX + ':'
432: + ENTRY_NODE_NAME);
433: }
434: }
435:
436: if (dayOpen) {
437: this .contentHandler.endElement(URI, DAY_NODE_NAME,
438: PREFIX + ':' + DAY_NODE_NAME);
439: }
440: if (monthOpen) {
441: this .contentHandler.endElement(URI, MONTH_NODE_NAME,
442: PREFIX + ':' + MONTH_NODE_NAME);
443: }
444: if (yearOpen) {
445: this .contentHandler.endElement(URI, YEAR_NODE_NAME,
446: PREFIX + ':' + YEAR_NODE_NAME);
447: }
448:
449: } catch (Exception e) {
450: throw new RuntimeException(e);
451: } finally {
452: if (selector != null) {
453: if (siteManager != null) {
454: selector.release(siteManager);
455: }
456: this .manager.release(selector);
457: }
458: }
459:
460: this .contentHandler.endElement(URI, BLOG_NODE_NAME, PREFIX
461: + ':' + BLOG_NODE_NAME);
462:
463: this.contentHandler.endPrefixMapping(PREFIX);
464: this.contentHandler.endDocument();
465: }
466: }
|