001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. The ASF licenses this file to You
004: * under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License. For additional information regarding
015: * copyright in this work, please see the NOTICE file in the top level
016: * directory of this distribution.
017: */
018:
019: package org.apache.roller.ui.rendering.servlets;
020:
021: import java.io.IOException;
022: import java.util.HashMap;
023: import java.util.Map;
024: import javax.servlet.ServletConfig;
025: import javax.servlet.ServletException;
026: import javax.servlet.http.HttpServlet;
027: import javax.servlet.http.HttpServletRequest;
028: import javax.servlet.http.HttpServletResponse;
029: import org.apache.commons.logging.Log;
030: import org.apache.commons.logging.LogFactory;
031: import org.apache.roller.RollerException;
032: import org.apache.roller.business.RollerFactory;
033: import org.apache.roller.business.WeblogManager;
034: import org.apache.roller.config.RollerConfig;
035: import org.apache.roller.config.RollerRuntimeConfig;
036: import org.apache.roller.pojos.StaticTemplate;
037: import org.apache.roller.pojos.Template;
038: import org.apache.roller.pojos.WebsiteData;
039: import org.apache.roller.ui.rendering.util.WeblogFeedRequest;
040: import org.apache.roller.util.cache.CachedContent;
041: import org.apache.roller.ui.rendering.Renderer;
042: import org.apache.roller.ui.rendering.RendererManager;
043: import org.apache.roller.ui.rendering.model.ModelLoader;
044: import org.apache.roller.ui.rendering.util.cache.SiteWideCache;
045: import org.apache.roller.ui.rendering.util.cache.WeblogFeedCache;
046: import org.apache.roller.ui.rendering.util.ModDateHeaderUtil;
047:
048: /**
049: * Responsible for rendering weblog feeds.
050: *
051: * @web.servlet name="FeedServlet" load-on-startup="5"
052: * @web.servlet-mapping url-pattern="/roller-ui/rendering/feed/*"
053: */
054: public class FeedServlet extends HttpServlet {
055:
056: private static Log log = LogFactory.getLog(FeedServlet.class);
057:
058: private WeblogFeedCache weblogFeedCache = null;
059: private SiteWideCache siteWideCache = null;
060:
061: /**
062: * Init method for this servlet
063: */
064: public void init(ServletConfig servletConfig)
065: throws ServletException {
066:
067: super .init(servletConfig);
068:
069: log.info("Initializing FeedServlet");
070:
071: // get a reference to the weblog feed cache
072: this .weblogFeedCache = WeblogFeedCache.getInstance();
073:
074: // get a reference to the site wide cache
075: this .siteWideCache = SiteWideCache.getInstance();
076: }
077:
078: /**
079: * Handle GET requests for weblog feeds.
080: */
081: public void doGet(HttpServletRequest request,
082: HttpServletResponse response) throws ServletException,
083: IOException {
084:
085: log.debug("Entering");
086:
087: WebsiteData weblog = null;
088: boolean isSiteWide = false;
089:
090: WeblogFeedRequest feedRequest = null;
091: try {
092: // parse the incoming request and extract the relevant data
093: feedRequest = new WeblogFeedRequest(request);
094:
095: weblog = feedRequest.getWeblog();
096: if (weblog == null) {
097: throw new RollerException("unable to lookup weblog: "
098: + feedRequest.getWeblogHandle());
099: }
100:
101: // is this the site-wide weblog?
102: isSiteWide = RollerRuntimeConfig
103: .isSiteWideWeblog(feedRequest.getWeblogHandle());
104:
105: } catch (Exception e) {
106: // invalid feed request format or weblog doesn't exist
107: log.debug("error creating weblog feed request", e);
108: response.sendError(HttpServletResponse.SC_NOT_FOUND);
109: return;
110: }
111:
112: // determine the lastModified date for this content
113: long lastModified = System.currentTimeMillis();
114: if (isSiteWide) {
115: lastModified = siteWideCache.getLastModified().getTime();
116: } else if (weblog.getLastModified() != null) {
117: lastModified = weblog.getLastModified().getTime();
118: }
119:
120: // Respond with 304 Not Modified if it is not modified.
121: if (ModDateHeaderUtil.respondIfNotModified(request, response,
122: lastModified)) {
123: return;
124: }
125:
126: // set last-modified date
127: ModDateHeaderUtil.setLastModifiedHeader(response, lastModified);
128:
129: // set content type
130: String accepts = request.getHeader("Accept");
131: String userAgent = request.getHeader("User-Agent");
132: if (RollerRuntimeConfig
133: .getBooleanProperty("site.newsfeeds.styledFeeds")
134: && accepts != null
135: && accepts.indexOf("*/*") != -1
136: && userAgent != null && userAgent.startsWith("Mozilla")) {
137: // client is a browser and feed style is enabled so we want
138: // browsers to load the page rather than popping up the download
139: // dialog, so we provide a content-type that browsers will display
140: response.setContentType("text/xml");
141: } else if ("rss".equals(feedRequest.getFormat())) {
142: response
143: .setContentType("application/rss+xml; charset=utf-8");
144: } else if ("atom".equals(feedRequest.getFormat())) {
145: response
146: .setContentType("application/atom+xml; charset=utf-8");
147: }
148:
149: // generate cache key
150: String cacheKey = null;
151: if (isSiteWide) {
152: cacheKey = siteWideCache.generateKey(feedRequest);
153: } else {
154: cacheKey = weblogFeedCache.generateKey(feedRequest);
155: }
156:
157: // cached content checking
158: CachedContent cachedContent = null;
159: if (isSiteWide) {
160: cachedContent = (CachedContent) siteWideCache.get(cacheKey);
161: } else {
162: cachedContent = (CachedContent) weblogFeedCache.get(
163: cacheKey, lastModified);
164: }
165:
166: if (cachedContent != null) {
167: log.debug("HIT " + cacheKey);
168:
169: response
170: .setContentLength(cachedContent.getContent().length);
171: response.getOutputStream()
172: .write(cachedContent.getContent());
173: return;
174:
175: } else {
176: log.debug("MISS " + cacheKey);
177: }
178:
179: // validation. make sure that request input makes sense.
180: boolean invalid = false;
181: if (feedRequest.getLocale() != null) {
182:
183: // locale view only allowed if weblog has enabled it
184: if (!feedRequest.getWeblog().isEnableMultiLang()) {
185: invalid = true;
186: }
187:
188: }
189: if (feedRequest.getWeblogCategoryName() != null) {
190:
191: // category specified. category must exist.
192: if (feedRequest.getWeblogCategory() == null) {
193: invalid = true;
194: }
195:
196: } else if (feedRequest.getTags() != null
197: && feedRequest.getTags().size() > 0) {
198:
199: try {
200: // tags specified. make sure they exist.
201: WeblogManager wmgr = RollerFactory.getRoller()
202: .getWeblogManager();
203: invalid = !wmgr.getTagComboExists(
204: feedRequest.getTags(), (isSiteWide) ? null
205: : weblog);
206: } catch (RollerException ex) {
207: invalid = true;
208: }
209: }
210:
211: if (invalid) {
212: if (!response.isCommitted())
213: response.reset();
214: response.sendError(HttpServletResponse.SC_NOT_FOUND);
215: return;
216: }
217:
218: // looks like we need to render content
219: HashMap model = new HashMap();
220: String pageId = null;
221: try {
222: // determine what template to render with
223: if (RollerRuntimeConfig
224: .isSiteWideWeblog(weblog.getHandle())) {
225: pageId = "templates/feeds/site-"
226: + feedRequest.getType() + "-"
227: + feedRequest.getFormat() + ".vm";
228: } else {
229: pageId = "templates/feeds/weblog-"
230: + feedRequest.getType() + "-"
231: + feedRequest.getFormat() + ".vm";
232: }
233:
234: // populate the rendering model
235: Map initData = new HashMap();
236: initData.put("request", request);
237: initData.put("weblogRequest", feedRequest);
238:
239: // Load models for feeds
240: String feedModels = RollerConfig
241: .getProperty("rendering.feedModels");
242: ModelLoader.loadModels(feedModels, model, initData, true);
243:
244: // Load special models for site-wide blog
245:
246: if (RollerRuntimeConfig
247: .isSiteWideWeblog(weblog.getHandle())) {
248: String siteModels = RollerConfig
249: .getProperty("rendering.siteModels");
250: ModelLoader.loadModels(siteModels, model, initData,
251: true);
252: }
253:
254: // Load weblog custom models
255: ModelLoader.loadCustomModels(weblog, model, initData);
256:
257: } catch (RollerException ex) {
258: log.error("ERROR loading model for page", ex);
259:
260: if (!response.isCommitted())
261: response.reset();
262: response
263: .sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
264: return;
265: }
266:
267: // lookup Renderer we are going to use
268: Renderer renderer = null;
269: try {
270: log.debug("Looking up renderer");
271: Template template = new StaticTemplate(pageId, null,
272: "velocity");
273: renderer = RendererManager.getRenderer(template);
274: } catch (Exception e) {
275: // nobody wants to render my content :(
276:
277: // TODO: this log message has been disabled because it fills up
278: // the logs with useless errors due to the fact that the way these
279: // template ids are formed comes directly from the request and it
280: // often gets bunk data causing invalid template ids.
281: // at some point we should have better validation on the input so
282: // that we can quickly dispatch invalid feed requests and only
283: // get this far if we expect the template to be found
284: //log.error("Couldn't find renderer for page "+pageId, e);
285:
286: if (!response.isCommitted())
287: response.reset();
288: response.sendError(HttpServletResponse.SC_NOT_FOUND);
289: return;
290: }
291:
292: // render content. use default size of about 24K for a standard page
293: CachedContent rendererOutput = new CachedContent(24567);
294: try {
295: log.debug("Doing rendering");
296: renderer.render(model, rendererOutput.getCachedWriter());
297:
298: // flush rendered output and close
299: rendererOutput.flush();
300: rendererOutput.close();
301: } catch (Exception e) {
302: // bummer, error during rendering
303: log.error("Error during rendering for page " + pageId, e);
304:
305: if (!response.isCommitted())
306: response.reset();
307: response.sendError(HttpServletResponse.SC_NOT_FOUND);
308: return;
309: }
310:
311: // post rendering process
312:
313: // flush rendered content to response
314: log.debug("Flushing response output");
315: response.setContentLength(rendererOutput.getContent().length);
316: response.getOutputStream().write(rendererOutput.getContent());
317:
318: // cache rendered content. only cache if user is not logged in?
319: log.debug("PUT " + cacheKey);
320: if (isSiteWide) {
321: siteWideCache.put(cacheKey, rendererOutput);
322: } else {
323: weblogFeedCache.put(cacheKey, rendererOutput);
324: }
325:
326: log.debug("Exiting");
327: }
328:
329: }
|