Java tutorial
/* * Copyright 2005 David M Johnson * * 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 com.manning.blogapps.chapter11; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; import java.util.Comparator; import java.util.Date; /* * Copyright 2005 Roller project * * 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. */ import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.VelocityEngine; import org.apache.velocity.texen.Generator; import org.jdom.Document; import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.input.SAXBuilder; import org.jdom.xpath.XPath; import com.manning.blogapps.chapter11.pojos.PlanetConfigData; import com.manning.blogapps.chapter11.pojos.PlanetEntryData; import com.manning.blogapps.chapter11.pojos.PlanetGroupData; import com.manning.blogapps.chapter11.pojos.PlanetSubscriptionData; import com.manning.blogapps.chapter11.utils.Utilities; /** * Utility that aggregates multiple newsfeeds using Rome Fetcher and calls * Velocity Texen control template generate files (HTML, RSS, OPML, etc.). * Does everything in memory; no database storage is used. * * @author David M Johnson */ public class PlanetTool extends PlanetManagerImpl { private static Log logger = LogFactory.getFactory().getInstance(PlanetTool.class); protected PlanetConfigData config = null; protected Map subsByURL = new HashMap(); // keys are URL strings protected Map groupsByHandle = new HashMap(); // keys are handle strings protected Map aggregationsByGroup = new HashMap(); // keys are GroupData objects /** * Construct by reading confuration JDOM Document. */ public PlanetTool(Document doc) throws Exception { try { initFromXML(doc); } catch (JDOMException e) { throw new Exception("Extracting config from parsed XML", e); } } /** * Call Texen control template specified by configuration to generate files. */ public void generatePlanet() throws Exception { try { VelocityEngine engine = new VelocityEngine(); engine.setProperty("resource.loader", "file"); engine.setProperty("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.FileResourceLoader"); engine.setProperty("file.resource.loader.path", getConfiguration().getTemplateDir()); engine.init(); VelocityContext context = new VelocityContext(); context.put("date", new Date()); context.put("utilities", new Utilities()); context.put("planet", this); File outputDir = new File(getConfiguration().getOutputDir()); if (!outputDir.exists()) outputDir.mkdirs(); Generator generator = Generator.getInstance(); generator.setVelocityEngine(engine); generator.setOutputEncoding("utf-8"); generator.setInputEncoding("utf-8"); generator.setOutputPath(getConfiguration().getOutputDir()); generator.setTemplatePath(getConfiguration().getTemplateDir()); generator.parse(config.getMainPage(), context); generator.shutdown(); } catch (Exception e) { e.printStackTrace(); throw new Exception("Writing planet files", e); } } public void saveConfiguration(PlanetConfigData config) throws Exception { this.config = config; } public void saveGroup(PlanetGroupData sub) throws Exception { groupsByHandle.put(sub.getHandle(), sub); } public void saveSubscription(PlanetSubscriptionData sub) throws Exception { subsByURL.put(sub.getFeedUrl(), sub); } public void saveEntry(PlanetEntryData entry) throws Exception { // no-op } public PlanetSubscriptionData getSubscription(String feedUrl) throws Exception { return (PlanetSubscriptionData) subsByURL.get(feedUrl); } public PlanetConfigData getConfiguration() throws Exception { return config; } public List getGroupHandles() throws Exception { return new ArrayList(groupsByHandle.keySet()); } public List getGroups() throws Exception { return new ArrayList(groupsByHandle.values()); } public PlanetGroupData getGroup(String handle) throws Exception { return (PlanetGroupData) groupsByHandle.get(handle); } public List getAggregation(PlanetGroupData group, int maxEntries) throws Exception { long startTime = System.currentTimeMillis(); List aggregation = null; try { // Get aggregation from cache aggregation = (List) aggregationsByGroup.get(group); if (aggregation == null) { // No aggregation found in cache, let's create a new one aggregation = new ArrayList(); // Comparator to help us create reverse chrono sorted list of entries Comparator entryDateComparator = new EntryDateComparator(); // Add all of group's subscription's entries to ordered collection Set sortedEntries = new TreeSet(entryDateComparator); Iterator subs = group.getSubscriptions().iterator(); while (subs.hasNext()) { PlanetSubscriptionData sub = (PlanetSubscriptionData) subs.next(); Iterator candidates = sub.getEntries().iterator(); while (candidates.hasNext()) { PlanetEntryData candidate = (PlanetEntryData) candidates.next(); if (group.qualified(candidate)) { sortedEntries.add(candidate); } } } // Throw away all but first maxEntris of our new entry list int count = 0; Iterator entries = sortedEntries.iterator(); while (entries.hasNext() && count++ < maxEntries) { aggregation.add(entries.next()); } aggregationsByGroup.put(group, aggregation); } } catch (Exception e) { logger.error("ERROR: building aggregation for: " + group.getHandle(), e); throw new Exception(e); } long endTime = System.currentTimeMillis(); logger.info("Generated aggregation in " + ((endTime - startTime) / 1000.0) + " seconds"); return aggregation; } public void deleteEntry(PlanetEntryData entry) throws Exception { // no-op } public void deleteGroup(PlanetGroupData group) throws Exception { // no-op } public void deleteSubscription(PlanetSubscriptionData group) throws Exception { // no-op } public void clearCachedAggregations() { // no-op } public List getTopSubscriptions(int max) throws Exception { throw new RuntimeException("NOT SUPPORTED"); } public List getTopSubscriptions(PlanetGroupData group, int max) throws Exception { throw new RuntimeException("NOT SUPPORTED"); } public PlanetSubscriptionData getSubscriptionById(String id) throws Exception { throw new RuntimeException("NOT SUPPORTED"); } public PlanetGroupData getGroupById(String id) throws Exception { throw new RuntimeException("NOT SUPPORTED"); } public List getAggregation(int maxEntries) throws Exception { throw new RuntimeException("NOT SUPPORTED"); } public Date getLastUpdated() { throw new RuntimeException("NOT SUPPORTED"); } public Date getLastUpdated(PlanetGroupData group) { throw new RuntimeException("NOT SUPPORTED"); } //--------------------------------------------------------------------- console public static void main(String[] args) { String success = "Planet complete!"; String error = null; Exception traceWorthy = null; String fileName = "planet-config.xml"; if (args.length == 1) { fileName = args[0]; } try { SAXBuilder builder = new SAXBuilder(); Document doc = builder.build(new FileInputStream(fileName)); PlanetTool planet = new PlanetTool(doc); planet.refreshEntries(); planet.generatePlanet(); System.out.println(success); System.exit(0); } catch (FileNotFoundException fnfe) { error = "Configuration file [" + fileName + "] not found"; } catch (JDOMException jde) { error = "Error parsing configuration file [" + fileName + "]"; traceWorthy = jde; } catch (IOException ioe) { error = "IO error. Using configuration file [" + fileName + "]"; traceWorthy = ioe; } catch (Exception re) { error = re.getMessage(); } if (error != null) { System.out.println(error); if (traceWorthy != null) traceWorthy.printStackTrace(); System.exit(-1); } } public Iterator getAllSubscriptions() { return subsByURL.values().iterator(); } //-------------------------------------------------------------------- privates /** * Load config data from XML using XPath. */ protected void initFromXML(Document doc) throws Exception, JDOMException { Map subsByID = new HashMap(); Element elem = doc.getRootElement(); config = new PlanetConfigData(); config.setCacheDir(getString(elem, "/planet-config/cache-dir")); config.setMainPage(getString(elem, "/planet-config/main-page")); config.setGroupPage(getString(elem, "/planet-config/group-page")); config.setAdminName(getString(elem, "/planet-config/admin-name")); config.setAdminEmail(getString(elem, "/planet-config/admin-email")); config.setSiteUrl(getString(elem, "/planet-config/site-url")); config.setOutputDir(getString(elem, "/planet-config/output-dir")); config.setTemplateDir(getString(elem, "/planet-config/template-dir")); config.setTitle(getString(elem, "/planet-config/title")); config.setDescription(getString(elem, "/planet-config/description")); XPath subsPath = XPath.newInstance("/planet-config/subscription"); Iterator subs = subsPath.selectNodes(doc).iterator(); while (subs.hasNext()) { Element subElem = (Element) subs.next(); PlanetSubscriptionData sub = new PlanetSubscriptionData(); String id = subElem.getAttributeValue("id"); sub.setTitle(getString(subElem, "title")); sub.setAuthor(getString(subElem, "author")); sub.setFeedUrl(getString(subElem, "feed-url")); sub.setSiteUrl(getString(subElem, "site-url")); subsByURL.put(sub.getFeedUrl(), sub); subsByID.put(id, sub); } logger.info("Found " + subsByID.size() + " subscriptions"); XPath groupsPath = XPath.newInstance("/planet-config/group"); Iterator groups = groupsPath.selectNodes(doc).iterator(); while (groups.hasNext()) { Element groupElem = (Element) groups.next(); PlanetGroupData group = new PlanetGroupData(); group.setHandle(groupElem.getAttributeValue("handle")); group.setTitle(getString(groupElem, "title")); group.setDescription(getString(groupElem, "description")); group.setMaxFeedEntries(getInt(groupElem, "max-feed-entries")); group.setMaxPageEntries(getInt(groupElem, "max-page-entries")); group.setCategoryRestriction(getString(groupElem, "category-restriction")); XPath refsPath = XPath.newInstance("subscription-ref"); Iterator refs = refsPath.selectNodes(groupElem).iterator(); while (refs.hasNext()) { Element refElem = (Element) refs.next(); String includeAll = refElem.getAttributeValue("include-all"); if (includeAll != null && includeAll.equals("true")) { //group.getSubscriptions().addAll(subsByID.values()); group.addSubscriptions(subsByID.values()); } else { String refid = refElem.getAttributeValue("refid"); PlanetSubscriptionData sub = (PlanetSubscriptionData) subsByID.get(refid); if (sub == null) { throw new Exception("No such subscription [" + refid + "]"); } //group.getSubscriptions().add(sub); group.addSubscription(sub); } } groupsByHandle.put(group.getHandle(), group); } logger.info("Found " + groupsByHandle.size() + " groups"); } //--------------------------------------------------------------- utilities protected String getString(Element elem, String path) throws JDOMException { XPath xpath = XPath.newInstance(path); Element e = (Element) xpath.selectSingleNode(elem); return e != null ? e.getText() : null; } protected int getInt(Element elem, String path) throws JDOMException { XPath xpath = XPath.newInstance(path); Element e = (Element) xpath.selectSingleNode(elem); return e != null ? Integer.parseInt(e.getText()) : 0; } public class EntryDateComparator implements Comparator { public int compare(Object o1, Object o2) { PlanetEntryData e1 = (PlanetEntryData) o1; PlanetEntryData e2 = (PlanetEntryData) o2; if (e1.getPublished() != null && e2.getPublished() != null) { return e2.getPublished().compareTo(e1.getPublished()); } if (e1.getPublished() == null) { logger.warn("Entry missing pubDate in sub: " + e1.getSubscription().getFeedUrl()); } if (e2.getPublished() == null) { logger.warn("Entry missing pubDate in sub: " + e2.getSubscription().getFeedUrl()); } return 0; } } /** * Total number of subscriptions. */ public int getSubscriptionCount() throws Exception { return this.subsByURL.size(); } }