blob: ed5f6a9a12a028b4fd8ec30f5eef3252c11e4ecc [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. The ASF licenses this file to You
* 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. For additional information regarding
* copyright in this work, please see the NOTICE file in the top level
* directory of this distribution.
*/
package org.apache.roller.weblogger.business.themes;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.activation.FileTypeMap;
import javax.activation.MimetypesFileTypeMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.roller.weblogger.WebloggerException;
import org.apache.roller.weblogger.business.InitializationException;
import org.apache.roller.weblogger.business.MediaFileManager;
import org.apache.roller.weblogger.business.WeblogManager;
import org.apache.roller.weblogger.business.Weblogger;
import org.apache.roller.weblogger.business.WebloggerFactory;
import org.apache.roller.weblogger.config.WebloggerConfig;
import org.apache.roller.weblogger.pojos.CustomTemplateRendition;
import org.apache.roller.weblogger.pojos.MediaFile;
import org.apache.roller.weblogger.pojos.MediaFileDirectory;
import org.apache.roller.weblogger.pojos.TemplateRendition;
import org.apache.roller.weblogger.pojos.TemplateRendition.RenditionType;
import org.apache.roller.weblogger.pojos.Theme;
import org.apache.roller.weblogger.pojos.ThemeResource;
import org.apache.roller.weblogger.pojos.ThemeTemplate;
import org.apache.roller.weblogger.pojos.ThemeTemplate.ComponentType;
import org.apache.roller.weblogger.pojos.Weblog;
import org.apache.roller.weblogger.pojos.WeblogTemplate;
import org.apache.roller.weblogger.pojos.WeblogTheme;
import org.apache.roller.weblogger.util.RollerMessages;
/**
* Base implementation of a ThemeManager.
*
* This particular implementation reads theme data off the filesystem and
* assumes that those themes are not changeable at runtime.
*/
@com.google.inject.Singleton
public class ThemeManagerImpl implements ThemeManager {
static FileTypeMap map = null;
static {
// TODO: figure out why PNG is missing from Java MIME types
map = FileTypeMap.getDefaultFileTypeMap();
if (map instanceof MimetypesFileTypeMap) {
try {
((MimetypesFileTypeMap) map).addMimeTypes("image/png png PNG");
} catch (Exception ignored) {
}
}
}
private static Log log = LogFactory.getLog(ThemeManagerImpl.class);
private final Weblogger roller;
// directory where themes are kept
private String themeDir = null;
// the Map contains ... (theme id, Theme)
private Map<String, SharedTheme> themes = null;
@com.google.inject.Inject
protected ThemeManagerImpl(Weblogger roller) {
this.roller = roller;
// get theme directory from config and verify it
this.themeDir = WebloggerConfig.getProperty("themes.dir");
if (themeDir == null || themeDir.trim().length() < 1) {
throw new RuntimeException(
"couldn't get themes directory from config");
} else {
// chop off trailing slash if it exists
if (themeDir.endsWith("/")) {
themeDir = themeDir.substring(0, themeDir.length() - 1);
}
// make sure it exists and is readable
File themeDirFile = new File(themeDir);
if (!themeDirFile.exists() || !themeDirFile.isDirectory()
|| !themeDirFile.canRead()) {
throw new RuntimeException("couldn't access theme dir ["
+ themeDir + "]");
}
}
}
public void initialize() throws InitializationException {
log.debug("Initializing Theme Manager");
if (themeDir != null) {
// rather than be lazy we are going to load all themes from
// the disk preemptive and cache them
this.themes = loadAllThemesFromDisk();
log.info("Successfully loaded " + this.themes.size() + " themes from disk.");
}
}
/**
* @see org.apache.roller.weblogger.business.themes.ThemeManager#getTheme(java.lang.String)
*/
public SharedTheme getTheme(String id) throws WebloggerException {
// try to lookup theme from library
SharedTheme theme = this.themes.get(id);
// no theme? throw exception.
if (theme == null) {
throw new ThemeNotFoundException("Couldn't find theme [" + id + "]");
}
return theme;
}
/**
* @see org.apache.roller.weblogger.business.themes.ThemeManager#getTheme(Weblog)
*/
public WeblogTheme getTheme(Weblog weblog) throws WebloggerException {
if (weblog == null) {
return null;
}
WeblogTheme weblogTheme = null;
// if theme is custom or null then return a WeblogCustomTheme
if (weblog.getEditorTheme() == null
|| WeblogTheme.CUSTOM.equals(weblog.getEditorTheme())) {
weblogTheme = new WeblogCustomTheme(weblog);
// otherwise we are returning a WeblogSharedTheme
} else {
SharedTheme staticTheme = (SharedTheme) this.themes.get(weblog
.getEditorTheme());
if (staticTheme != null) {
weblogTheme = new WeblogSharedTheme(weblog, staticTheme);
} else {
log.warn("Unable to lookup theme " + weblog.getEditorTheme());
}
}
// TODO: if somehow the theme is still null should we provide some
// kind of fallback option like a default theme?
return weblogTheme;
}
/**
* @see org.apache.roller.weblogger.business.themes.ThemeManager#getEnabledThemesList()
*/
public List<SharedTheme> getEnabledThemesList() {
List<SharedTheme> allThemes = new ArrayList<SharedTheme>(this.themes.values());
// sort 'em ... default ordering for themes is by name
Collections.sort(allThemes);
return allThemes;
}
/**
* @see org.apache.roller.weblogger.business.themes.ThemeManager#importTheme(Weblog,
* SharedTheme, boolean)
*/
public void importTheme(Weblog weblog, SharedTheme theme, boolean skipStylesheet)
throws WebloggerException {
log.debug("Importing theme [" + theme.getName() + "] to weblog ["
+ weblog.getName() + "]");
WeblogManager wmgr = roller.getWeblogManager();
MediaFileManager fileMgr = roller.getMediaFileManager();
MediaFileDirectory root = fileMgr.getDefaultMediaFileDirectory(weblog);
if (root == null) {
log.warn("Weblog " + weblog.getHandle() + " does not have a root MediaFile directory");
}
Set<ComponentType> importedActionTemplates = new HashSet<ComponentType>();
ThemeTemplate stylesheetTemplate = theme.getStylesheet();
for (ThemeTemplate themeTemplate : theme.getTemplates()) {
WeblogTemplate template;
// if template is an action, lookup by action
if (themeTemplate.getAction() != null
&& !themeTemplate.getAction().equals(ComponentType.CUSTOM)) {
importedActionTemplates.add(themeTemplate.getAction());
template = wmgr.getTemplateByAction(weblog,
themeTemplate.getAction());
// otherwise, lookup by name
} else {
template = wmgr.getTemplateByName(weblog, themeTemplate.getName());
}
// Weblog does not have this template, so create it.
boolean newTmpl = false;
if (template == null) {
template = new WeblogTemplate();
template.setWeblog(weblog);
newTmpl = true;
}
// update template attributes except leave existing custom stylesheets as-is
if (!themeTemplate.equals(stylesheetTemplate) || !skipStylesheet) {
template.setAction(themeTemplate.getAction());
template.setName(themeTemplate.getName());
template.setDescription(themeTemplate.getDescription());
template.setLink(themeTemplate.getLink());
template.setHidden(themeTemplate.isHidden());
template.setNavbar(themeTemplate.isNavbar());
template.setOutputContentType(themeTemplate.getOutputContentType());
template.setLastModified(new Date());
// save it
wmgr.saveTemplate(template);
// create weblog template code objects and save them
for (RenditionType type : RenditionType.values()) {
// See if we already have some code for this template already (eg previous theme)
CustomTemplateRendition weblogTemplateCode = template.getTemplateRendition(type);
// Get the template for the new theme
TemplateRendition templateCode = themeTemplate.getTemplateRendition(type);
if (templateCode != null) {
// Check for existing template
if (weblogTemplateCode == null) {
// Does not exist so create a new one
weblogTemplateCode = new CustomTemplateRendition(template, type);
}
weblogTemplateCode.setType(type);
weblogTemplateCode.setTemplate(templateCode.getTemplate());
weblogTemplateCode.setTemplateLanguage(templateCode
.getTemplateLanguage());
WebloggerFactory.getWeblogger().getWeblogManager()
.saveTemplateRendition(weblogTemplateCode);
}
}
}
}
// now, see if the weblog has left over non-custom action templates that
// need to be deleted because they aren't in their new theme
for (ComponentType action : ComponentType.values()) {
if (action == ComponentType.CUSTOM) {
continue;
}
// if we didn't import this action then see if it should be deleted
if (!importedActionTemplates.contains(action)) {
WeblogTemplate toDelete = wmgr.getTemplateByAction(weblog, action);
if (toDelete != null) {
log.debug("Removing stale action template " + toDelete.getId());
wmgr.removeTemplate(toDelete);
}
}
}
// set weblog's theme to custom, then save
weblog.setEditorTheme(WeblogTheme.CUSTOM);
wmgr.saveWeblog(weblog);
// now lets import all the theme resources
for (ThemeResource resource : theme.getResources()) {
log.debug("Importing resource " + resource.getPath());
if (resource.isDirectory()) {
MediaFileDirectory mdir = fileMgr.getMediaFileDirectoryByName(
weblog, resource.getPath());
if (mdir == null) {
log.debug(" Creating directory: " + resource.getPath());
fileMgr.createMediaFileDirectory(weblog, resource.getPath());
roller.flush();
} else {
log.debug(" No action: directory already exists");
}
} else {
String resourcePath = resource.getPath();
MediaFileDirectory mdir;
String justName;
String justPath;
if (resourcePath.indexOf('/') == -1) {
mdir = fileMgr.getDefaultMediaFileDirectory(weblog);
justPath = "";
justName = resourcePath;
} else {
justPath = resourcePath.substring(0,
resourcePath.lastIndexOf('/'));
if (!justPath.startsWith("/")) {
justPath = "/" + justPath;
}
justName = resourcePath.substring(resourcePath
.lastIndexOf('/') + 1);
mdir = fileMgr.getMediaFileDirectoryByName(weblog,
justPath);
if (mdir == null) {
log.debug(" Creating directory: " + justPath);
mdir = fileMgr.createMediaFileDirectory(weblog,
justPath);
roller.flush();
}
}
MediaFile oldmf = fileMgr.getMediaFileByOriginalPath(weblog,
justPath + "/" + justName);
if (oldmf != null) {
fileMgr.removeMediaFile(weblog, oldmf);
}
// save file without file-type, quota checks, etc.
InputStream is = resource.getInputStream();
MediaFile mf = new MediaFile();
mf.setDirectory(mdir);
mf.setWeblog(weblog);
mf.setName(justName);
mf.setOriginalPath(justPath + "/" + justName);
mf.setContentType(map.getContentType(justName));
mf.setInputStream(is);
mf.setLength(resource.getLength());
log.debug(" Saving file: " + justName);
log.debug(" Saving in directory = " + mf.getDirectory());
RollerMessages errors = new RollerMessages();
fileMgr.createMediaFile(weblog, mf, errors);
try {
resource.getInputStream().close();
} catch (IOException ex) {
errors.addError("error.closingStream");
log.debug("ERROR closing inputstream");
}
if (errors.getErrorCount() > 0) {
throw new WebloggerException(errors.toString());
}
roller.flush();
}
}
}
/**
* This is a convenience method which loads all the theme data from themes
* stored on the filesystem in the roller webapp /themes/ directory.
*/
private Map<String, SharedTheme> loadAllThemesFromDisk() {
Map<String, SharedTheme> themeMap = new HashMap<String, SharedTheme>();
// first, get a list of the themes available
File themesdir = new File(this.themeDir);
FilenameFilter filter = new FilenameFilter() {
public boolean accept(File dir, String name) {
File file = new File(dir.getAbsolutePath() + File.separator
+ name);
return file.isDirectory() && !file.getName().startsWith(".");
}
};
String[] themenames = themesdir.list(filter);
if (themenames == null) {
log.warn("No themes found! Perhaps wrong directory for themes specified? "
+ "(Check themes.dir setting in roller[-custom].properties file.)");
} else {
log.info("Loading themes from " + themesdir.getAbsolutePath() + "...");
// now go through each theme and load it into a Theme object
for (String themeName : themenames) {
try {
SharedTheme theme = new SharedThemeFromDir(this.themeDir
+ File.separator + themeName);
themeMap.put(theme.getId(), theme);
log.info("Loaded theme '" + themeName + "'");
} catch (Exception unexpected) {
// shouldn't happen, so let's learn why it did
log.error("Problem processing theme '" + themeName + "':", unexpected);
}
}
}
return themeMap;
}
/**
* @see ThemeManager#reLoadThemeFromDisk(String)
*/
public boolean reLoadThemeFromDisk(String reloadTheme) {
boolean reloaded = false;
try {
SharedTheme theme = new SharedThemeFromDir(this.themeDir + File.separator
+ reloadTheme);
Theme loadedTheme = themes.get(theme.getId());
if (loadedTheme != null
&& theme.getLastModified().after(
loadedTheme.getLastModified())) {
themes.remove(theme.getId());
themes.put(theme.getId(), theme);
reloaded = true;
}
} catch (Exception unexpected) {
// shouldn't happen, so let's learn why it did
log.error("Problem reloading theme " + reloadTheme, unexpected);
}
return reloaded;
}
}