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.business;
020:
021: import java.io.File;
022: import java.io.FileInputStream;
023: import java.io.FileNotFoundException;
024: import java.io.FilenameFilter;
025: import java.io.InputStreamReader;
026: import java.util.ArrayList;
027: import java.util.Collection;
028: import java.util.Collections;
029: import java.util.Date;
030: import java.util.HashMap;
031: import java.util.Iterator;
032: import java.util.List;
033: import java.util.Map;
034: import org.apache.commons.logging.Log;
035: import org.apache.commons.logging.LogFactory;
036: import org.apache.roller.RollerException;
037: import org.apache.roller.business.ThemeNotFoundException;
038: import org.apache.roller.config.RollerConfig;
039: import org.apache.roller.business.FileIOException;
040: import org.apache.roller.business.FileManager;
041: import org.apache.roller.business.FilePathException;
042: import org.apache.roller.business.RollerFactory;
043: import org.apache.roller.business.ThemeManager;
044: import org.apache.roller.business.UserManager;
045: import org.apache.roller.pojos.Theme;
046: import org.apache.roller.pojos.ThemeTemplate;
047: import org.apache.roller.pojos.WeblogTemplate;
048: import org.apache.roller.pojos.WebsiteData;
049:
050: /**
051: * Base implementation of a ThemeManager.
052: *
053: * This particular implementation reads theme data off the filesystem
054: * and assumes that those themes are not changable at runtime.
055: */
056: public class ThemeManagerImpl implements ThemeManager {
057:
058: private static Log log = LogFactory.getLog(ThemeManagerImpl.class);
059:
060: // directory where themes are kept
061: private String themeDir = null;
062:
063: // the Map contains ... (theme name, Theme)
064: private Map themes = null;
065:
066: protected ThemeManagerImpl() {
067:
068: log.debug("Initializing ThemeManagerImpl");
069:
070: // get theme directory from config and verify it
071: this .themeDir = RollerConfig.getProperty("themes.dir");
072: if (themeDir == null || themeDir.trim().length() < 1) {
073: throw new RuntimeException(
074: "couldn't get themes directory from config");
075: } else {
076: // chop off trailing slash if it exists
077: if (themeDir.endsWith("/")) {
078: themeDir = themeDir.substring(0, themeDir.length() - 1);
079: }
080:
081: // make sure it exists and is readable
082: File themeDirFile = new File(themeDir);
083: if (!themeDirFile.exists() || !themeDirFile.isDirectory()
084: || !themeDirFile.canRead()) {
085: throw new RuntimeException(
086: "couldn't access theme dir [" + themeDir + "]");
087: }
088:
089: // rather than be lazy we are going to load all themes from
090: // the disk preemptively during initialization and cache them
091: this .themes = loadAllThemesFromDisk();
092:
093: log.info("Loaded " + this .themes.size()
094: + " themes from disk.");
095: }
096: }
097:
098: /**
099: * @see org.apache.roller.model.ThemeManager#getTheme(java.lang.String)
100: */
101: public Theme getTheme(String name) throws ThemeNotFoundException,
102: RollerException {
103:
104: Theme theme = (Theme) this .themes.get(name);
105: if (theme == null)
106: throw new ThemeNotFoundException("Couldn't find theme ["
107: + name + "]");
108:
109: return theme;
110: }
111:
112: /**
113: * @see org.apache.roller.model.ThemeManager#getEnabledThemesList()
114: */
115: public List getEnabledThemesList() {
116:
117: Collection all_themes = this .themes.values();
118:
119: // make a new list of only the enabled themes
120: List enabled_themes = new ArrayList();
121: Iterator it = all_themes.iterator();
122: Theme theme = null;
123: while (it.hasNext()) {
124: theme = (Theme) it.next();
125: if (theme.isEnabled())
126: enabled_themes.add(theme.getName());
127: }
128:
129: // sort 'em ... the natural sorting order for Strings is alphabetical
130: Collections.sort(enabled_themes);
131:
132: return enabled_themes;
133: }
134:
135: /**
136: * @see org.apache.roller.model.ThemeManager#importTheme(website, theme)
137: */
138: public void importTheme(WebsiteData website, Theme theme)
139: throws RollerException {
140:
141: log.debug("Importing theme " + theme.getName() + " to weblog "
142: + website.getName());
143:
144: try {
145: UserManager userMgr = RollerFactory.getRoller()
146: .getUserManager();
147:
148: Collection templates = theme.getTemplates();
149: Iterator iter = templates.iterator();
150: ThemeTemplate theme_template = null;
151: while (iter.hasNext()) {
152: theme_template = (ThemeTemplate) iter.next();
153:
154: WeblogTemplate template = null;
155:
156: if (theme_template.getName().equals(
157: WeblogTemplate.DEFAULT_PAGE)) {
158: // this is the main Weblog template
159: try {
160: template = userMgr.getPage(website
161: .getDefaultPageId());
162: } catch (Exception e) {
163: // user may not have a default page yet
164: }
165: } else {
166: // any other template
167: template = userMgr.getPageByName(website,
168: theme_template.getName());
169: }
170:
171: if (template != null) {
172: // User already has page by that name, so overwrite it.
173: template.setContents(theme_template.getContents());
174: template.setLink(theme_template.getLink());
175:
176: } else {
177: // User does not have page by that name, so create new page.
178: template = new WeblogTemplate(null, // id
179: website, // website
180: theme_template.getName(), // name
181: theme_template.getDescription(), // description
182: theme_template.getLink(), // link
183: theme_template.getContents(), // contents
184: new Date(), // last mod
185: theme_template.getTemplateLanguage(), // temp lang
186: theme_template.isHidden(), // hidden
187: theme_template.isNavbar(), // navbar
188: theme_template.getDecoratorName() // decorator
189: );
190: userMgr.savePage(template);
191: }
192: }
193:
194: // now update this website's theme to custom
195: website.setEditorTheme(Theme.CUSTOM);
196:
197: // if this is the first time someone is customizing a theme then
198: // we need to set a default page
199: if (website.getDefaultPageId() == null
200: || website.getDefaultPageId().trim().equals("")
201: || website.getDefaultPageId().equals("dummy")) {
202: // we have to go back to the db to figure out the id
203: WeblogTemplate template = userMgr.getPageByName(
204: website, "Weblog");
205: if (template != null) {
206: log.debug("Setting default page to "
207: + template.getId());
208: website.setDefaultPageId(template.getId());
209: }
210: }
211:
212: // we also want to set the weblogdayid
213: WeblogTemplate dayTemplate = userMgr.getPageByName(website,
214: "_day");
215: if (dayTemplate != null) {
216: log.debug("Setting default day page to "
217: + dayTemplate.getId());
218: website.setWeblogDayPageId(dayTemplate.getId());
219: }
220:
221: // save our updated website
222: userMgr.saveWebsite(website);
223:
224: // now lets import all the theme resources
225: FileManager fileMgr = RollerFactory.getRoller()
226: .getFileManager();
227:
228: List resources = theme.getResources();
229: Iterator iterat = resources.iterator();
230: File resourceFile = null;
231: while (iterat.hasNext()) {
232: resourceFile = (File) iterat.next();
233:
234: String path = resourceFile.getAbsolutePath().substring(
235: this .themeDir.length()
236: + theme.getName().length() + 1);
237:
238: // make sure path isn't prefixed with a /
239: if (path.startsWith("/")) {
240: path = path.substring(1);
241: }
242:
243: log.debug("Importing resource "
244: + resourceFile.getAbsolutePath() + " to "
245: + path);
246:
247: try {
248: if (resourceFile.isDirectory()) {
249: fileMgr.createDirectory(website, path);
250: } else {
251: fileMgr.saveFile(website, path, "text/plain",
252: resourceFile.length(),
253: new FileInputStream(resourceFile));
254: }
255: } catch (Exception ex) {
256: log.info(ex);
257: }
258: }
259:
260: } catch (Exception e) {
261: log.error("ERROR in action", e);
262: throw new RollerException(e);
263: }
264: }
265:
266: /**
267: * This is a convenience method which loads all the theme data from
268: * themes stored on the filesystem in the roller webapp /themes/ directory.
269: */
270: private Map loadAllThemesFromDisk() {
271:
272: Map themes = new HashMap();
273:
274: String themespath = RollerConfig.getProperty("themes.dir");
275: if (themespath.endsWith(File.separator)) {
276: themespath.substring(0, themespath.length() - 1);
277: }
278:
279: // first, get a list of the themes available
280: File themesdir = new File(themespath);
281: FilenameFilter filter = new FilenameFilter() {
282: public boolean accept(File dir, String name) {
283: File file = new File(dir.getAbsolutePath()
284: + File.separator + name);
285: return file.isDirectory();
286: }
287: };
288: String[] themenames = themesdir.list(filter);
289:
290: if (themenames == null)
291: themenames = new String[0];
292:
293: // now go through each theme and read all it's templates
294: Theme theme = null;
295: for (int i = 0; i < themenames.length; i++) {
296: try {
297: theme = this .loadThemeFromDisk(themenames[i],
298: themespath + File.separator + themenames[i]);
299: themes.put(theme.getName(), theme);
300: } catch (Throwable unexpected) {
301: // shouldn't happen, so let's learn why it did
302: log.error("Problem reading theme " + themenames[i],
303: unexpected);
304: }
305: }
306:
307: return themes;
308: }
309:
310: /**
311: * Another convenience method which knows how to load a single theme
312: * off the filesystem and return a Theme object
313: */
314: private Theme loadThemeFromDisk(String theme_name, String themepath) {
315:
316: log.info("Loading theme " + theme_name);
317:
318: Theme theme = new Theme();
319: theme.setName(theme_name);
320: theme.setAuthor("Roller");
321: theme.setLastEditor("Roller");
322: theme.setEnabled(true);
323:
324: // start by getting a list of the .vm files for this theme
325: File themedir = new File(themepath);
326: FilenameFilter filter = new FilenameFilter() {
327: public boolean accept(File dir, String name) {
328: return name.endsWith(".vm");
329: }
330: };
331: String[] templates = themedir.list(filter);
332:
333: // go through each .vm file and read in its contents to a ThemeTemplate
334: String template_name = null;
335: ThemeTemplate theme_template = null;
336: for (int i = 0; i < templates.length; i++) {
337: // strip off the .vm part
338: template_name = templates[i].substring(0, templates[i]
339: .length() - 3);
340: File template_file = new File(themepath + File.separator
341: + templates[i]);
342:
343: // Continue reading theme even if problem encountered with one file
344: String msg = "read theme template file [" + template_file
345: + "]";
346: if (!template_file.exists() && !template_file.canRead()) {
347: log.error("Couldn't " + msg);
348: continue;
349: }
350: char[] chars = null;
351: int length;
352: try {
353: // FileReader reader = new FileReader(template_file);
354: chars = new char[(int) template_file.length()];
355: FileInputStream stream = new FileInputStream(
356: template_file);
357: InputStreamReader reader = new InputStreamReader(
358: stream, "UTF-8");
359: length = reader.read(chars);
360: } catch (Exception noprob) {
361: log.error("Exception while attempting to " + msg);
362: if (log.isDebugEnabled())
363: log.debug(noprob);
364: continue;
365: }
366:
367: // Strip "_" from name to form link
368: boolean navbar = true;
369: String template_link = template_name;
370: if (template_name.startsWith("_")
371: && template_name.length() > 1) {
372: navbar = false;
373: template_link = template_link.substring(1);
374: log.debug("--- " + template_link);
375: }
376:
377: String decorator = "_decorator";
378: if ("_decorator".equals(template_name)) {
379: decorator = null;
380: }
381:
382: // construct ThemeTemplate representing this file
383: // a few restrictions for now:
384: // - we only allow "velocity" for the template language
385: // - decorator is always "_decorator" or null
386: // - all theme templates are considered not hidden
387: theme_template = new ThemeTemplate(theme, theme_name + ":"
388: + template_name, template_name, template_name,
389: new String(chars, 0, length), template_link,
390: new Date(template_file.lastModified()), "velocity",
391: false, navbar, decorator);
392:
393: // add it to the theme
394: theme.setTemplate(template_name, theme_template);
395: }
396:
397: // use the last mod date of the theme dir
398: theme.setLastModified(new Date(themedir.lastModified()));
399:
400: // load up resources as well
401: loadThemeResources(theme, themedir, themedir.getAbsolutePath());
402:
403: return theme;
404: }
405:
406: /**
407: * Convenience method for loading a theme's resource files.
408: * This method works recursively to load from subdirectories.
409: */
410: private void loadThemeResources(Theme theme, File workPath,
411: String basePath) {
412:
413: // now go through all static resources for this theme
414: FilenameFilter resourceFilter = new FilenameFilter() {
415: public boolean accept(File dir, String name) {
416: return !name.endsWith(".vm");
417: }
418: };
419: File[] resources = workPath.listFiles(resourceFilter);
420:
421: // go through each resource file and add it to the theme
422: String resourcePath = null;
423: File resourceFile = null;
424: for (int i = 0; i < resources.length; i++) {
425: resourceFile = resources[i];
426: resourcePath = resourceFile.getAbsolutePath().substring(
427: basePath.length() + 1);
428:
429: log.debug("handling resource [" + resourcePath + "]");
430:
431: // Continue reading theme even if problem encountered with one file
432: if (!resourceFile.exists() || !resourceFile.canRead()) {
433: log.warn("Couldn't read theme resource file ["
434: + resourcePath + "]");
435: continue;
436: }
437:
438: // if its a directory, recurse
439: if (resourceFile.isDirectory()) {
440: log.debug("resource is a directory, recursing");
441: loadThemeResources(theme, resourceFile, basePath);
442: }
443:
444: // otherwise just add the File to the theme
445: theme.setResource(resourcePath, resourceFile);
446: }
447: }
448:
449: }
|