You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@roller.apache.org by ag...@apache.org on 2006/09/29 02:31:38 UTC

svn commit: r451071 - in /incubator/roller/trunk: src/org/apache/roller/business/ src/org/apache/roller/config/ src/org/apache/roller/model/ src/org/apache/roller/pojos/ src/org/apache/roller/ui/authoring/struts/actions/ src/org/apache/roller/ui/core/ ...

Author: agilliland
Date: Thu Sep 28 17:31:37 2006
New Revision: 451071

URL: http://svn.apache.org/viewvc?view=rev&rev=451071
Log:
First commit for Roller 3.1 Theme Encapsulation work ...

http://rollerweblogger.org/wiki/Wiki.jsp?page=Proposal_ThemeEncapsulation

1. Reworking of FileManager implementation to support storing files in a hierarchy using subdirectories.

  * Cleaned up FileManager interface and added new createDirectory() method.
  * Added new getFile() method which provides access to a specific file.
  * Removed methods which exposed the uploads dir and uploads path.
  * Removed canSave() method because it was redundant and should be implied by the call to saveFile().
  * Added better exception handling and comments.

2. Updated themes code to support the handling of theme resource files.

  * Added code for resource handling in Theme class.
  * Added code in ThemeManagerImpl for reading in Theme resources and properly setting them.
  * Cleaned up unnecessary methods from ThemeManager interface.

3. Updated ResourceServlet to be capable of serving up theme resources.

  * Added WeblogResourceRequest object.
  * ResourceServlet now checks if weblog is using a theme and if so checks if the requested resource comes from the theme.

NOTE: this work only partially implements the overall functionality of the proposal.  there will be further commits to complete the work.


Added:
    incubator/roller/trunk/src/org/apache/roller/model/FileIOException.java
    incubator/roller/trunk/src/org/apache/roller/model/FileNotFoundException.java
    incubator/roller/trunk/src/org/apache/roller/model/FilePathException.java
    incubator/roller/trunk/src/org/apache/roller/ui/rendering/util/WeblogResourceRequest.java
Modified:
    incubator/roller/trunk/src/org/apache/roller/business/FileManagerImpl.java
    incubator/roller/trunk/src/org/apache/roller/business/ThemeManagerImpl.java
    incubator/roller/trunk/src/org/apache/roller/config/RollerConfig.java
    incubator/roller/trunk/src/org/apache/roller/model/FileManager.java
    incubator/roller/trunk/src/org/apache/roller/model/ThemeManager.java
    incubator/roller/trunk/src/org/apache/roller/pojos/Theme.java
    incubator/roller/trunk/src/org/apache/roller/ui/authoring/struts/actions/ImportEntriesAction.java
    incubator/roller/trunk/src/org/apache/roller/ui/authoring/struts/actions/UploadFileFormAction.java
    incubator/roller/trunk/src/org/apache/roller/ui/core/RollerContext.java
    incubator/roller/trunk/src/org/apache/roller/ui/rendering/servlets/ResourceServlet.java
    incubator/roller/trunk/src/org/apache/roller/ui/rendering/velocity/deprecated/ContextLoader.java
    incubator/roller/trunk/src/org/apache/roller/webservices/atomprotocol/RollerAtomHandler.java
    incubator/roller/trunk/src/org/apache/roller/webservices/xmlrpc/MetaWeblogAPIHandler.java
    incubator/roller/trunk/tests/org/apache/roller/business/FileManagerTest.java
    incubator/roller/trunk/web/WEB-INF/classes/roller.properties

Modified: incubator/roller/trunk/src/org/apache/roller/business/FileManagerImpl.java
URL: http://svn.apache.org/viewvc/incubator/roller/trunk/src/org/apache/roller/business/FileManagerImpl.java?view=diff&rev=451071&r1=451070&r2=451071
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/business/FileManagerImpl.java (original)
+++ incubator/roller/trunk/src/org/apache/roller/business/FileManagerImpl.java Thu Sep 28 17:31:37 2006
@@ -20,33 +20,32 @@
 
 import java.io.File;
 import java.io.FileOutputStream;
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.math.BigDecimal;
-import java.util.Map;
 import org.apache.commons.lang.StringUtils;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
-import org.apache.roller.RollerException;
 import org.apache.roller.config.RollerConfig;
 import org.apache.roller.config.RollerRuntimeConfig;
+import org.apache.roller.model.FileIOException;
+import org.apache.roller.model.FilePathException;
 import org.apache.roller.model.FileManager;
-import org.apache.roller.model.Roller;
-import org.apache.roller.model.RollerFactory;
-import org.apache.roller.pojos.RollerPropertyData;
+import org.apache.roller.model.FileNotFoundException;
 import org.apache.roller.util.RollerMessages;
 
 
 /**
- * Responsible for managing website resources.  This base implementation
- * writes resources to a filesystem.
+ * Manages files uploaded to Roller.  
+ * 
+ * This base implementation writes resources to a filesystem.
  */
 public class FileManagerImpl implements FileManager {
     
-    private String upload_dir = null;
-    private String upload_url = null;
+    private static Log log = LogFactory.getLog(FileManagerImpl.class);
     
-    private static Log mLogger = LogFactory.getLog(FileManagerImpl.class);
+    private String upload_dir = null;
     
     
     /**
@@ -54,7 +53,6 @@
      */
     public FileManagerImpl() {
         String uploaddir = RollerConfig.getProperty("uploads.dir");
-        String uploadurl = RollerConfig.getProperty("uploads.url");
         
         // Note: System property expansion is now handled by RollerConfig.
         
@@ -64,201 +62,263 @@
         if( ! uploaddir.endsWith(File.separator))
             uploaddir += File.separator;
         
-        if(uploadurl == null || uploadurl.trim().length() < 1)
-            uploadurl = File.separator+"resources";
-        
         this.upload_dir = uploaddir.replace('/',File.separatorChar);
-        this.upload_url = uploadurl;
-    }
-    
-    
-    /**
-     * Get the upload directory being used by this file manager
-     **/
-    public String getUploadDir() {
-        return this.upload_dir;
     }
     
     
     /**
-     * Get the upload path being used by this file manager
-     **/
-    public String getUploadUrl() {
-        return this.upload_url;
+     * @see org.apache.roller.model.FileManager#getFile(java.lang.String, java.lang.String)
+     */
+    public File getFile(String weblogHandle, String path) 
+            throws FileNotFoundException, FilePathException {
+        
+        // get a reference to the file, checks that file exists & is readable
+        File resourceFile = this.getRealFile(weblogHandle, path);
+        
+        // make sure file is not a directory
+        if(resourceFile.isDirectory()) {
+            throw new FilePathException("Invalid path ["+path+"], "+
+                    "path is a directory.");
+        }
+        
+        // everything looks good, return file
+        return resourceFile;
     }
     
     
     /**
-     * Determine if file can be saved given current RollerConfig settings.
+     * @see org.apache.roller.model.FileManager#getFiles(java.lang.String, java.lang.String)
      */
-    public boolean canSave(String weblogHandle, String name, String contentType,
-                           long size, RollerMessages messages)
-            throws RollerException {
+    public File[] getFiles(String weblogHandle, String path) 
+            throws FileNotFoundException, FilePathException {
         
-        Roller mRoller = RollerFactory.getRoller();
-        Map config = mRoller.getPropertiesManager().getProperties();
+        // get a reference to the dir, checks that dir exists & is readable
+        File dirFile = this.getRealFile(weblogHandle, path);
         
-        if (!((RollerPropertyData)config.get("uploads.enabled")).getValue().equalsIgnoreCase("true")) {
-            messages.addError("error.upload.disabled");
-            return false;
+        // make sure path is a directory
+        if(!dirFile.isDirectory()) {
+            throw new FilePathException("Invalid path ["+path+"], "+
+                    "path is not a directory.");
         }
         
-        String allows = ((RollerPropertyData)config.get("uploads.types.allowed")).getValue();
-        String forbids = ((RollerPropertyData)config.get("uploads.types.forbid")).getValue();
-        String[] allowFiles = StringUtils.split(StringUtils.deleteWhitespace(allows), ",");
-        String[] forbidFiles = StringUtils.split(StringUtils.deleteWhitespace(forbids), ",");
-        if (!checkFileType(allowFiles, forbidFiles, name, contentType)) {
-            messages.addError("error.upload.forbiddenFile", allows);
-            return false;
-        }
+        // everything looks good, list contents
+        return dirFile.listFiles();
+    }
+    
+    
+    /**
+     * @see org.apache.roller.model.FileManager#saveFile(java.lang.String, java.lang.String, java.lang.String, long, java.io.InputStream)
+     */
+    public void saveFile(String weblogHandle, 
+                         String path, 
+                         String contentType, 
+                         long size, 
+                         InputStream is)
+            throws FileNotFoundException, FilePathException, FileIOException {
+        
+        // make sure we are allowed to save this file
+        RollerMessages msgs = new RollerMessages();
+        if (!canSave(weblogHandle, path, contentType, size, msgs)) {
+            throw new FileIOException(msgs.toString());
+        }
+        
+        // make sure uploads area exists for this weblog
+        File dirPath = this.getRealFile(weblogHandle, null);
+        File saveFile = new File(dirPath.getAbsolutePath() + File.separator + path);
         
-        BigDecimal maxDirMB = new BigDecimal(
-                ((RollerPropertyData)config.get("uploads.dir.maxsize")).getValue());
-        int maxDirBytes = (int)(1024000 * maxDirMB.doubleValue());
-        int userDirSize = getWebsiteDirSize(weblogHandle, this.upload_dir);
-        if (userDirSize + size > maxDirBytes) {
-            messages.addError("error.upload.dirmax", maxDirMB.toString());
-            return false;
+        byte[] buffer = new byte[8192];
+        int bytesRead = 0;
+        OutputStream bos = null;
+        try {
+            bos = new FileOutputStream(saveFile);
+            while ((bytesRead = is.read(buffer, 0, 8192)) != -1) {
+                bos.write(buffer, 0, bytesRead);
+            }
+            
+            log.debug("The file has been written to ["+saveFile.getAbsolutePath()+"]");
+        } catch (Exception e) {
+            throw new FileIOException("ERROR uploading file", e);
+        } finally {
+            try {
+                bos.flush();
+                bos.close();
+            } catch (Exception ignored) {}
         }
         
-        BigDecimal maxFileMB = new BigDecimal(
-                ((RollerPropertyData)config.get("uploads.file.maxsize")).getValue());
-        int maxFileBytes = (int)(1024000 * maxFileMB.doubleValue());
-        mLogger.debug(""+maxFileBytes);
-        mLogger.debug(""+size);
-        if (size > maxFileBytes) {
-            messages.addError("error.upload.filemax", maxFileMB.toString());
-            return false;
-        }
         
-        return true;
     }
     
     
-    public boolean overQuota(String weblogHandle) throws RollerException {
+    /**
+     * @see org.apache.roller.model.FileManager#createDirectory(java.lang.String, java.lang.String)
+     */
+    public void createDirectory(String weblogHandle, String path)
+            throws FileNotFoundException, FilePathException, FileIOException {
         
-        String maxDir = RollerRuntimeConfig.getProperty("uploads.dir.maxsize");
-        String maxFile = RollerRuntimeConfig.getProperty("uploads.file.maxsize");
-        BigDecimal maxDirSize = new BigDecimal(maxDir); // in megabytes
-        BigDecimal maxFileSize = new BigDecimal(maxFile); // in megabytes
+        // get path to weblog's uploads area
+        File uploadDir = this.getRealFile(weblogHandle, null);
         
-        // determine the number of bytes in website's directory
-        int maxDirBytes = (int)(1024000 * maxDirSize.doubleValue());
-        int userDirSize = 0;
-        String dir = getUploadDir();
-        File d = new File(dir + weblogHandle);
-        if (d.mkdirs() || d.exists()) {
-            File[] files = d.listFiles();
-            long dirSize = 0l;
-            for (int i=0; i<files.length; i++) {
-                if (!files[i].isDirectory()) {
-                    dirSize = dirSize + files[i].length();
-                }
-            }
-            userDirSize = new Long(dirSize).intValue();
+        // now construct path to new directory
+        File dir = new File(uploadDir.getAbsolutePath() + File.separator + path);
+        
+        // create it
+        if(!dir.mkdir()) {
+            // failed for some reason
+            throw new FileIOException("Failed to create directory ["+path+"], "+
+                    "probably doesn't have needed parent directories.");
         }
-        return userDirSize > maxDirBytes;
     }
     
     
     /**
-     * Get collection files in website's resource directory.
-     * @param site Website
-     * @return Collection of files in website's resource directory
+     * @see org.apache.roller.model.FileManager#deleteFile(java.lang.String, java.lang.String)
      */
-    public File[] getFiles(String weblogHandle) throws RollerException {
-        String dir = this.upload_dir + weblogHandle;
-        File uploadDir = new File(dir);
-        return uploadDir.listFiles();
+    public void deleteFile(String weblogHandle, String path) 
+            throws FileNotFoundException, FilePathException, FileIOException {
+        
+        // get path to delete file, checks that path exists and is readable
+        File delFile = this.getRealFile(weblogHandle, path);
+        
+        if(!delFile.delete()) {
+            throw new FileIOException("Delete failed for ["+path+"], "+
+                    "possibly a non-empty directory?");
+        }
     }
     
     
     /**
-     * Delete named file from website's resource area.
+     * @see org.apache.roller.model.FileManager#overQuota(java.lang.String)
      */
-    public void deleteFile(String weblogHandle, String name) 
-            throws RollerException {
-        String dir = this.upload_dir + weblogHandle;
-        File f = new File(dir + File.separator + name);
-        f.delete();
+    public boolean overQuota(String weblogHandle) {
+        
+        String maxDir = RollerRuntimeConfig.getProperty("uploads.dir.maxsize");
+        String maxFile = RollerRuntimeConfig.getProperty("uploads.file.maxsize");
+        BigDecimal maxDirSize = new BigDecimal(maxDir); // in megabytes
+        BigDecimal maxFileSize = new BigDecimal(maxFile); // in megabytes
+        
+        long maxDirBytes = (long)(1024000 * maxDirSize.doubleValue());
+        
+        try {
+            File uploadsDir = this.getRealFile(weblogHandle, null);
+            long weblogDirSize = this.getDirSize(uploadsDir, true);
+            
+            return weblogDirSize > maxDirBytes;
+        } catch (Exception ex) {
+            // shouldn't ever happen, this means user's uploads dir is bad
+            // rethrow as a runtime exception
+            throw new RuntimeException(ex);
+        }
+    }
+    
+    
+    public void release() {
     }
     
     
     /**
-     * Save file to website's resource directory.
-     * @param site Website to save to
-     * @param name Name of file to save
-     * @param size Size of file to be saved
-     * @param is Read file from input stream
+     * Determine if file can be saved given current RollerConfig settings.
      */
-    public void saveFile(String weblogHandle, String name, String contentType, 
-                         long size, InputStream is)
-            throws RollerException {
+    private boolean canSave(String weblogHandle, 
+                           String path, 
+                           String contentType,
+                           long size, 
+                           RollerMessages messages) {
         
-        if (!canSave(weblogHandle, name, contentType, size, new RollerMessages())) {
-            throw new RollerException("ERROR: upload denied");
+        // first check, is uploading enabled?
+        if(!RollerRuntimeConfig.getBooleanProperty("uploads.enabled")) {
+            messages.addError("error.upload.disabled");
+            return false;
         }
         
-        byte[] buffer = new byte[8192];
-        int bytesRead = 0;
-        String dir = this.upload_dir;
-        
-        File dirPath = new File(dir + File.separator + weblogHandle);
-        if (!dirPath.exists()) {
-            dirPath.mkdirs();
+        // second check, does upload exceed max size for file?
+        BigDecimal maxFileMB = new BigDecimal(
+                RollerRuntimeConfig.getProperty("uploads.file.maxsize"));
+        int maxFileBytes = (int)(1024000 * maxFileMB.doubleValue());
+        log.debug("max allowed file size = "+maxFileBytes);
+        log.debug("attempted save file size = "+size);
+        if (size > maxFileBytes) {
+            messages.addError("error.upload.filemax", maxFileMB.toString());
+            return false;
         }
-        OutputStream bos = null;
+        
+        // third check, does file cause weblog to exceed quota?
+        BigDecimal maxDirMB = new BigDecimal(
+                RollerRuntimeConfig.getProperty("uploads.dir.maxsize"));
+        long maxDirBytes = (long)(1024000 * maxDirMB.doubleValue());
         try {
-            bos = new FileOutputStream(
-                    dirPath.getAbsolutePath() + File.separator + name);
-            while ((bytesRead = is.read(buffer, 0, 8192)) != -1) {
-                bos.write(buffer, 0, bytesRead);
+            File uploadsDir = this.getRealFile(weblogHandle, null);
+            long userDirSize = getDirSize(uploadsDir, true);
+            if (userDirSize + size > maxDirBytes) {
+                messages.addError("error.upload.dirmax", maxDirMB.toString());
+                return false;
             }
-        } catch (Exception e) {
-            throw new RollerException("ERROR uploading file", e);
-        } finally {
-            try {
-                bos.flush();
-                bos.close();
-            } catch (Exception ignored) {}
+        } catch (Exception ex) {
+            // shouldn't ever happen, means the weblogs uploads dir is bad somehow
+            // rethrow as a runtime exception
+            throw new RuntimeException(ex);
         }
-        if (mLogger.isDebugEnabled()) {
-            mLogger.debug("The file has been written to \"" + dir + weblogHandle + "\"");
+        
+        // fourth check, is upload type allowed?
+        String allows = RollerRuntimeConfig.getProperty("uploads.types.allowed");
+        String forbids = RollerRuntimeConfig.getProperty("uploads.types.forbid");
+        String[] allowFiles = StringUtils.split(StringUtils.deleteWhitespace(allows), ",");
+        String[] forbidFiles = StringUtils.split(StringUtils.deleteWhitespace(forbids), ",");
+        if (!checkFileType(allowFiles, forbidFiles, path, contentType)) {
+            messages.addError("error.upload.forbiddenFile", allows);
+            return false;
+        }
+        
+        // fifth check, is save path viable?
+        if(path.indexOf("/") != -1) {
+            // see if directory path exists already
+            String dirPath = path.substring(0, path.lastIndexOf("/"));
+            
+            try {
+                File parent = this.getRealFile(weblogHandle, dirPath);
+                if(parent == null || !parent.exists()) {
+                    messages.addError("error.upload.badPath");
+                }
+            } catch (Exception ex) {
+                // this is okay, just means that parent dir doesn't exist
+                messages.addError("blah");
+                return false;
+            }
+            
         }
+        
+        return true;
     }
     
     
     /**
-     * Returns current size of file uploads owned by specified weblog handle.
-     * @param username User
-     * @param dir      Upload directory
-     * @return Size of user's uploaded files in bytes.
+     * Get the size in bytes of given directory.
+     *
+     * Optionally works recursively counting subdirectories if they exist.
      */
-    private int getWebsiteDirSize(String weblogHandle, String dir) {
+    private long getDirSize(File dir, boolean recurse) {
         
-        int userDirSize = 0;
-        File d = new File(dir + File.separator + weblogHandle);
-        if (d.mkdirs() || d.exists()) {
-            File[] files = d.listFiles();
+        long size = 0;
+        if(dir.exists() && dir.isDirectory() && dir.canRead()) {
+            File[] files = dir.listFiles();
             long dirSize = 0l;
-            for (int i=0; i<files.length; i++) {
+            for (int i=0; i < files.length; i++) {
                 if (!files[i].isDirectory()) {
-                    dirSize = dirSize + files[i].length();
+                    dirSize += files[i].length();
+                } else if(recurse) {
+                    // count a subdirectory
+                    dirSize += getDirSize(files[i], recurse);
                 }
             }
-            userDirSize = new Long(dirSize).intValue();
+            size += dirSize;
         }
-        return userDirSize;
+        
+        return size;
     }
     
     
     /**
      * Return true if file is allowed to be uplaoded given specified allowed and
      * forbidden file types.
-     * @param allowFiles  File types (i.e. extensions) that are allowed
-     * @param forbidFiles File types that are forbidden
-     * @param fileName    Name of file to be uploaded
-     * @return True if file is allowed to be uploaded
      */
     private boolean checkFileType(String[] allowFiles, String[] forbidFiles,
                                   String fileName, String contentType) {
@@ -358,7 +418,58 @@
     }
     
     
-    public void release() {
+    /**
+     * Construct the full real path to a resource in a weblog's uploads area.
+     */
+    private File getRealFile(String weblogHandle, String path) 
+            throws FileNotFoundException, FilePathException {
+        
+        // make sure uploads area exists for this weblog
+        File weblogDir = new File(this.upload_dir + weblogHandle);
+        if(!weblogDir.exists()) {
+            weblogDir.mkdirs();
+        }
+        
+        // crop leading slash if it exists
+        String relPath = path;
+        if(path != null && path.startsWith("/")) {
+            relPath = path.substring(1);
+        }
+        
+        // convert "/" to filesystem specific file separator
+        if(relPath != null) {
+            relPath.replaceAll("/", File.separator);
+        }
+        
+        // now form the absolute path
+        String filePath = weblogDir.getAbsolutePath();
+        if(relPath != null) {
+            filePath += File.separator + relPath;
+        }
+        
+        // make sure path exists and is readable
+        File file = new File(filePath);
+        if(!file.exists()) {
+            throw new FileNotFoundException("Invalid path ["+path+"], "+
+                    "directory doesn't exist.");
+        } else if(!file.canRead()) {
+            throw new FilePathException("Invalid path ["+path+"], "+
+                    "cannot read from path.");
+        }
+        
+        try {
+            // make sure someone isn't trying to sneek outside the uploads dir
+            File uploadDir = new File(this.upload_dir);
+            if(!file.getCanonicalPath().startsWith(uploadDir.getCanonicalPath())) {
+                throw new FilePathException("Invalid path ["+path+"], "+
+                        "trying to get outside uploads dir.");
+            }
+        } catch (IOException ex) {
+            // rethrow as FilePathException
+            throw new FilePathException(ex);
+        }
+        
+        return file;
     }
     
 }

Modified: incubator/roller/trunk/src/org/apache/roller/business/ThemeManagerImpl.java
URL: http://svn.apache.org/viewvc/incubator/roller/trunk/src/org/apache/roller/business/ThemeManagerImpl.java?view=diff&rev=451071&r1=451070&r2=451071
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/business/ThemeManagerImpl.java (original)
+++ incubator/roller/trunk/src/org/apache/roller/business/ThemeManagerImpl.java Thu Sep 28 17:31:37 2006
@@ -57,17 +57,41 @@
     
     private static Log log = LogFactory.getLog(ThemeManagerImpl.class);
     
+    // directory where themes are kept
+    private String themeDir = null;
+    
+    // the Map contains ... (theme name, Theme)
     private Map themes = null;
     
     
     protected ThemeManagerImpl() {
         
-        // rather than be lazy we are going to load all themes from
-        // the disk preemptively during initialization and cache them
         log.debug("Initializing ThemeManagerImpl");
         
-        this.themes = this.loadAllThemesFromDisk();
-        log.info("Loaded "+this.themes.size()+" themes from disk.");
+        // get theme directory from config and verify it
+        this.themeDir = RollerConfig.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+"]");
+            }
+            
+            // rather than be lazy we are going to load all themes from
+            // the disk preemptively during initialization and cache them
+            this.themes = loadAllThemesFromDisk();
+            
+            log.info("Loaded "+this.themes.size()+" themes from disk.");
+        }
     }
     
     
@@ -86,32 +110,6 @@
     
     
     /**
-     * @see org.apache.roller.model.ThemeManager#getThemeById(java.lang.String)
-     */
-    public Theme getThemeById(String id) 
-        throws ThemeNotFoundException, RollerException {
-        
-        // In this implementation where themes come from the filesystem we
-        // know that the name and id for a theme are the same
-        return this.getTheme(id);
-    }
-    
-    
-    /**
-     * @see org.apache.roller.model.ThemeManager#getThemesList()
-     */
-    public List getThemesList() {
-        
-        List themes = new ArrayList(this.themes.keySet());
-        
-        // sort 'em ... the natural sorting order for Strings is alphabetical
-        Collections.sort(themes);
-        
-        return themes;
-    }
-    
-    
-    /**
      * @see org.apache.roller.model.ThemeManager#getEnabledThemesList()
      */
     public List getEnabledThemesList() {
@@ -136,51 +134,6 @@
     
     
     /**
-     * @see org.apache.roller.model.ThemeManager#getTemplate(String, String)
-     */
-    public ThemeTemplate getTemplate(String theme_name, String template_name)
-        throws ThemeNotFoundException, RollerException {
-        
-        // basically we just try and lookup the theme first, then template
-        Theme theme = this.getTheme(theme_name);
-        
-        return theme.getTemplate(template_name);
-    }
-    
-    
-    /**
-     * @see org.apache.roller.model.ThemeManager#getTemplateById(java.lang.String)
-     */
-    public ThemeTemplate getTemplateById(String id)
-        throws ThemeNotFoundException, RollerException {
-        
-        if(id == null)
-            throw new ThemeNotFoundException("Theme id was null");
-        
-        // in our case we expect a template id to be <theme>:<template>
-        // so extract each piece and do the lookup
-        String[] split = id.split(":",  2);
-        if(split.length != 2)
-            throw new ThemeNotFoundException("Invalid theme id ["+id+"]");
-        
-        return this.getTemplate(split[0], split[1]);
-    }
-    
-    
-    /**
-     * @see org.apache.roller.model.ThemeManager#getTemplateByLink(java.lang.String)
-     */
-    public ThemeTemplate getTemplateByLink(String theme_name, String template_link)
-        throws ThemeNotFoundException, RollerException {
-        
-        // basically we just try and lookup the theme first, then template
-        Theme theme = this.getTheme(theme_name);
-        
-        return theme.getTemplateByLink(template_link);
-    }
-    
-    
-    /**
      * This is a convenience method which loads all the theme data from
      * themes stored on the filesystem in the roller webapp /themes/ directory.
      */
@@ -224,7 +177,8 @@
         
         return themes;
     }
-        
+    
+    
     /**
      * Another convenience method which knows how to load a single theme
      * off the filesystem and return a Theme object
@@ -314,13 +268,59 @@
             theme.setTemplate(template_name, theme_template);
         }
         
-        // use the last mod date of the last template file
-        // as the last mod date of the theme
-        theme.setLastModified(theme_template.getLastModified());
+        // use the last mod date of the theme dir
+        theme.setLastModified(new Date(themedir.lastModified()));
+        
+        // load up resources as well
+        loadThemeResources(theme, themedir, themedir.getAbsolutePath());
         
         return theme;
     }
-
+    
+    
+    /**
+     * Convenience method for loading a theme's resource files.
+     * This method works recursively to load from subdirectories.
+     */
+    private void loadThemeResources(Theme theme, File workPath, String basePath) {
+        
+        // now go through all static resources for this theme
+        FilenameFilter resourceFilter = new FilenameFilter()
+        {
+            public boolean accept(File dir, String name)
+            {
+                return !name.endsWith(".vm");
+            }
+        };
+        File[] resources = workPath.listFiles(resourceFilter);
+        
+        // go through each resource file and add it to the theme
+        String resourcePath = null;
+        File resourceFile = null;
+        for (int i=0; i < resources.length; i++) {
+            resourceFile = resources[i];
+            resourcePath = resourceFile.getAbsolutePath().substring(basePath.length()+1);
+            
+            log.debug("handling resource ["+resourcePath+"]");
+            
+            // Continue reading theme even if problem encountered with one file
+            if(!resourceFile.exists() || !resourceFile.canRead()) {
+                log.warn("Couldn't read theme resource file ["+resourcePath+"]");
+                continue;
+            }
+            
+            // if its a directory, recurse
+            if(resourceFile.isDirectory()) {
+                log.debug("resource is a directory, recursing");
+                loadThemeResources(theme, resourceFile, basePath);
+            }
+            
+            // otherwise just add the File to the theme
+            theme.setResource(resourcePath, resourceFile);
+        }
+    }
+    
+    
     /**
      * Helper method that copies down the pages from a given theme into a
      * users weblog templates.

Modified: incubator/roller/trunk/src/org/apache/roller/config/RollerConfig.java
URL: http://svn.apache.org/viewvc/incubator/roller/trunk/src/org/apache/roller/config/RollerConfig.java?view=diff&rev=451071&r1=451070&r2=451071
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/config/RollerConfig.java (original)
+++ incubator/roller/trunk/src/org/apache/roller/config/RollerConfig.java Thu Sep 28 17:31:37 2006
@@ -219,6 +219,21 @@
         if("${webapp.context}".equals(mConfig.getProperty("uploads.dir")))
             mConfig.setProperty("uploads.dir", path);
     }
+    
+    /**
+     * Set the "themes.dir" property at runtime.
+     * <p />
+     * Properties are meant to be read-only, but we make this exception because  
+     * we know that some people are still using their themes in the webapp  
+     * context and we can only get that path at runtime (and for unit testing).
+     * <p />
+     * This property is *not* persisted in any way.
+     */
+    public static void setThemesDir(String path) {
+        // only do this if the user wants to use the webapp context
+        if("${webapp.context}".equals(mConfig.getProperty("themes.dir")))
+            mConfig.setProperty("themes.dir", path);
+    }
 
     /**
      * Set the "context.realPath" property at runtime.

Added: incubator/roller/trunk/src/org/apache/roller/model/FileIOException.java
URL: http://svn.apache.org/viewvc/incubator/roller/trunk/src/org/apache/roller/model/FileIOException.java?view=auto&rev=451071
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/model/FileIOException.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/model/FileIOException.java Thu Sep 28 17:31:37 2006
@@ -0,0 +1,42 @@
+/*
+ * 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.model;
+
+import org.apache.roller.RollerException;
+
+
+/**
+ * Thrown from the FileManager if there is some kind of IO exception while
+ * working on a file, such as during a save or delete.
+ */
+public class FileIOException extends RollerException {
+    
+    public FileIOException(String s) {
+        super(s);
+    }
+    
+    public FileIOException(String s, Throwable t) {
+        super(s, t);
+    }
+    
+    public FileIOException(Throwable t) {
+        super(t);
+    }
+    
+}

Modified: incubator/roller/trunk/src/org/apache/roller/model/FileManager.java
URL: http://svn.apache.org/viewvc/incubator/roller/trunk/src/org/apache/roller/model/FileManager.java?view=diff&rev=451071&r1=451070&r2=451071
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/model/FileManager.java (original)
+++ incubator/roller/trunk/src/org/apache/roller/model/FileManager.java Thu Sep 28 17:31:37 2006
@@ -1,73 +1,140 @@
 /*
-* 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.
-*/
+ * 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.model;
 
 import java.io.File;
 import java.io.InputStream;
-import java.io.Serializable;
-
 import org.apache.roller.RollerException;
-import org.apache.roller.pojos.WebsiteData;
-import org.apache.roller.util.RollerMessages;
+
 
 /**
  * Interface for managing files uploaded to Roller.
- *
- * NOTE: this should probably be renamed "ResourceManager" or similar
- * since the real jobe here is managing resources, not just files.  We should
- * then extend this a bit more to include the notion of not only user uploaded
- * resources, but also other resources the system stores, such as the blacklist.
- *
- * @author dave
  */
-public interface FileManager extends Serializable 
-{
-    /** Determine if file can be saved in website's file space. */
-    public boolean canSave(
-        String weblogHandle, String name, String contentType, long size, RollerMessages msgs) 
-        throws RollerException;
-    
-    /** Get website's files */
-    public File[] getFiles(String weblogHandle) 
-        throws RollerException;
-    
-    /** Delete specified file from website's file space. */
-    public void deleteFile(String weblogHandle, String name) 
-        throws RollerException;
-
-    /** Save file in website's file space or throw exception if rules violated. */
-    public void saveFile(String weblogHandle, String name, String contentType, long size, InputStream is) 
-        throws RollerException;
-
-    /** Return true if weblog is over the file-upload limit */
-    public boolean overQuota(String weblogHandle) throws RollerException; 
-            
+public interface FileManager {
+    
+    /**
+     * Get a reference to a specific file in a weblog's uploads area.
+     * 
+     * This method always returns a valid file or will throw an exception
+     * if the specificed path doesn't exist, is a directory, or can't be read.
+     * 
+     * @param weblogHandle The handle of the weblog.
+     * @param path The relative path to the desired resource within 
+     * the weblog's uploads area.
+     *
+     * @throws FileNotFoundException If path does not exist.
+     * @throws FilePathException If path is invalid, is a directory, or can't be read.
+     *
+     * @return File representing the real file resource.
+     */
+    public File getFile(String weblogHandle, String path) 
+        throws FileNotFoundException, FilePathException;
+    
+    
     /**
-     * Get directory in which uploaded files are stored
+     * 
+     * Get list of files from a specific path of the weblog's uploads area.
+     * 
+     * This method will return a File[] array of all files at the given path if
+     * it exists, otherwise it will throw an exception.
+     * 
+     * This method should return the files at the root of the weblog's uploads
+     * area given a path value of null, "" (empty string), or "/"
+     * 
+     * @param weblogHandle The handle of the weblog.
+     * @param path The relative path to the desired resource within 
+     * the weblog's uploads area.
+     * 
+     * @throws FileNotFoundException If path does not exist.
+     * @throws FilePathException If path is invalid, is not a directory, or can't be read.
+     *
+     * @return File[] of files in website's uploads area at given path.
      */
-    public String getUploadDir();
+    public File[] getFiles(String weblogHandle, String path) 
+        throws FileNotFoundException, FilePathException;
+    
+    
+    /**
+     * Save a file to weblog's uploads area.
+     * 
+     * @param weblogHandle The handle of the weblog.
+     * @param path The relative path to the desired location within 
+     * the weblog's uploads area where the file should be saved.
+     * @param contentType Content type of the file.
+     * @param size Size of file to be saved.
+     * @param is InputStream to read the file from.
+     *
+     * @throws FileNotFoundException If path to save location does not exist.
+     * @throws FilePathException If path is invalid, is not a directory, or can't be read.
+     * @throws FileIOException If there is an unexpected error during the save.
+     */
+    public void saveFile(String weblogHandle, 
+                         String path, 
+                         String contentType, 
+                         long size,
+                         InputStream is) 
+        throws FileNotFoundException, FilePathException, FileIOException;
+    
+    
     /**
-     * Get base URL where uploaded files are made available.
+     * Create an empty subdirectory in the weblog's uploads area.
+     *
+     * @param weblogHandle The handle of the weblog.
+     * @param path The relative path to the desired location within 
+     * the weblog's uploads area where the directory should be created.
+     *
+     * @throws FileNotFoundException If path to create location does not exist.
+     * @throws FilePathException If path is invalid, is not a directory, or can't be read.
+     * @throws FileIOException If there is an unexpected error during the create.
      */
-    public String getUploadUrl();
+    public void createDirectory(String weblogHandle, String path) 
+        throws FileNotFoundException, FilePathException, FileIOException;
+    
+    
+    /**
+     * Delete file or directory from weblog's uploads area.
+     * 
+     * @param weblogHandle The handle of the weblog.
+     * @param path The relative path to the file within the weblog's uploads 
+     * area that should be deleted.
+     *
+     * @throws FileNotFoundException If path does not exist.
+     * @throws FilePathException If path is invalid, or can't be read.
+     * @throws FileIOException If there is an unexpected error during the delete.
+     */
+    public void deleteFile(String weblogHandle, String path) 
+        throws FileNotFoundException, FilePathException, FileIOException;
+    
+    
+    /** 
+     * Is the given weblog over the file-upload quota limit?
+     *
+     * @param weblogHandle The handle of the weblog.
+     *
+     * @return True if weblog is over set quota, False otherwise.
+     */
+    public boolean overQuota(String weblogHandle);
+    
     
     /**
      * Release all resources associated with Roller session.
      */
     public void release();
+    
 }

Added: incubator/roller/trunk/src/org/apache/roller/model/FileNotFoundException.java
URL: http://svn.apache.org/viewvc/incubator/roller/trunk/src/org/apache/roller/model/FileNotFoundException.java?view=auto&rev=451071
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/model/FileNotFoundException.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/model/FileNotFoundException.java Thu Sep 28 17:31:37 2006
@@ -0,0 +1,41 @@
+/*
+ * 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.model;
+
+import org.apache.roller.RollerException;
+
+
+/**
+ * Thrown from the FileManager if a file path does not exist.
+ */
+public class FileNotFoundException extends RollerException {
+    
+    public FileNotFoundException(String s) {
+        super(s);
+    }
+    
+    public FileNotFoundException(String s, Throwable t) {
+        super(s, t);
+    }
+    
+    public FileNotFoundException(Throwable t) {
+        super(t);
+    }
+    
+}

Added: incubator/roller/trunk/src/org/apache/roller/model/FilePathException.java
URL: http://svn.apache.org/viewvc/incubator/roller/trunk/src/org/apache/roller/model/FilePathException.java?view=auto&rev=451071
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/model/FilePathException.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/model/FilePathException.java Thu Sep 28 17:31:37 2006
@@ -0,0 +1,42 @@
+/*
+ * 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.model;
+
+import org.apache.roller.RollerException;
+
+
+/**
+ * Thrown from the FileManager if a file path is considered invalid for some
+ * reason, like it represents a directory instead of a file.
+ */
+public class FilePathException extends RollerException {
+    
+    public FilePathException(String s) {
+        super(s);
+    }
+    
+    public FilePathException(String s, Throwable t) {
+        super(s, t);
+    }
+    
+    public FilePathException(Throwable t) {
+        super(t);
+    }
+    
+}

Modified: incubator/roller/trunk/src/org/apache/roller/model/ThemeManager.java
URL: http://svn.apache.org/viewvc/incubator/roller/trunk/src/org/apache/roller/model/ThemeManager.java?view=diff&rev=451071&r1=451070&r2=451071
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/model/ThemeManager.java (original)
+++ incubator/roller/trunk/src/org/apache/roller/model/ThemeManager.java Thu Sep 28 17:31:37 2006
@@ -18,6 +18,7 @@
 
 package org.apache.roller.model;
 
+import java.io.File;
 import java.util.List;
 import org.apache.roller.RollerException;
 import org.apache.roller.ThemeNotFoundException;
@@ -42,25 +43,6 @@
     
     
     /**
-     * Get the Theme object with the given theme id.
-     *
-     * @throws ThemeNotFoundException If the named theme cannot be found.
-     * @throws RollerException If there is some kind of fatal backend error.
-     */
-    public Theme getThemeById(String theme_id)
-        throws ThemeNotFoundException, RollerException;
-    
-    
-    /**
-     * Get a list of all available themes.
-     * This list is ordered alphabetically by default.
-     *
-     * NOTE: this only returns a list of theme names, not actual Theme objects.
-     **/
-    public List getThemesList();
-    
-    
-    /**
      * Get a list of all theme names that are currently enabled.
      * This list is ordered alphabetically by default.
      *
@@ -70,42 +52,11 @@
     
     
     /**
-     * Get the template from a given theme.
-     *
-     * @throws ThemeNotFoundException If the named theme cannot be found.
-     * @throws RollerException If there is some kind of fatal backend error.
-     */
-    public ThemeTemplate getTemplate(String theme_name, String template_name)
-        throws ThemeNotFoundException, RollerException;
-    
-    
-    /**
-     * Get the template from a given theme using the template id.
+     * Save all the templates for a Theme into the given weblog.
      *
-     * Theme templates use a special id value when they come off the filesystem.
-     * When a theme is read off the filesystem it's templates are given an id
-     * like ... <theme name>:<template name>
-     *
-     * @throws ThemeNotFoundException If the named theme cannot be found.
-     * @throws RollerException If there is some kind of fatal backend error.
+     * @throws RollerException If there is some kind of error in saving.
      */
-    public ThemeTemplate getTemplateById(String template_id)
-        throws ThemeNotFoundException, RollerException;
-
+    public void saveThemePages(WebsiteData website, Theme theme) 
+        throws RollerException;
     
-    /**
-     * Get the template from a given theme using the template link value.
-     *
-     * Note that for themes we enforce the rule that 
-     *      Theme.name == Theme.link
-     *
-     * So doing a lookup by link is the same as doing a lookup by name.
-     *
-     * @throws ThemeNotFoundException If the named theme cannot be found.
-     * @throws RollerException If there is some kind of fatal backend error.
-     */
-    public ThemeTemplate getTemplateByLink(String theme_name, String template_link)
-        throws ThemeNotFoundException, RollerException;
-   
-    public void saveThemePages(WebsiteData website, Theme theme) throws RollerException;
 }

Modified: incubator/roller/trunk/src/org/apache/roller/pojos/Theme.java
URL: http://svn.apache.org/viewvc/incubator/roller/trunk/src/org/apache/roller/pojos/Theme.java?view=diff&rev=451071&r1=451070&r2=451071
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/pojos/Theme.java (original)
+++ incubator/roller/trunk/src/org/apache/roller/pojos/Theme.java Thu Sep 28 17:31:37 2006
@@ -18,6 +18,7 @@
 
 package org.apache.roller.pojos;
 
+import java.io.File;
 import java.io.Serializable;
 import java.util.Collection;
 import java.util.Date;
@@ -48,6 +49,10 @@
     // the Map contains ... (template name, ThemeTemplate)
     private Map templates;
     
+    // we keep resources in a Map for faster lookups by path
+    // the Map contains ... (resource path, File)
+    private Map resources;
+    
     
     public Theme() {
         this.id = null;
@@ -58,6 +63,7 @@
         this.lastModified = null;
         this.enabled = false;
         this.templates = new HashMap();
+        this.resources = new HashMap();
     }
 
     
@@ -106,6 +112,40 @@
      */
     public boolean hasTemplate(String name) {
         return this.templates.containsKey(name);
+    }
+    
+    
+    /**
+     * Get the collection of all resources associated with this Theme.
+     */
+    public Collection getResources() {
+        return this.resources.values();
+    }
+    
+    
+    /**
+     * Lookup the specified resource by path.
+     * Returns null if the resource cannot be found.
+     */
+    public File getResource(String path) {
+        return (File) this.resources.get(path);
+    }
+    
+    
+    /**
+     * Set the value for a given resource path.
+     */
+    public void setResource(String path, File resource) {
+        this.resources.put(path, resource);
+    }
+    
+    
+    /**
+     * Check if this Theme contains the named resource.
+     * Returns true if the resource exists, false otherwise.
+     */
+    public boolean hasResource(String path) {
+        return this.resources.containsKey(path);
     }
     
     

Modified: incubator/roller/trunk/src/org/apache/roller/ui/authoring/struts/actions/ImportEntriesAction.java
URL: http://svn.apache.org/viewvc/incubator/roller/trunk/src/org/apache/roller/ui/authoring/struts/actions/ImportEntriesAction.java?view=diff&rev=451071&r1=451070&r2=451071
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/ui/authoring/struts/actions/ImportEntriesAction.java (original)
+++ incubator/roller/trunk/src/org/apache/roller/ui/authoring/struts/actions/ImportEntriesAction.java Thu Sep 28 17:31:37 2006
@@ -43,6 +43,7 @@
 import org.apache.roller.util.cache.CacheManager;
 import org.apache.roller.ui.authoring.struts.formbeans.ImportEntriesForm;
 import org.apache.commons.lang.StringUtils;
+import org.apache.roller.model.FileManager;
 
 /**
  * TODO: revisit this class once Atom 1.0 support comes to Rome
@@ -82,9 +83,8 @@
                     WebsiteData website = rreq.getWebsite();
 
                     // load selected file
-                    String dir = RollerFactory.getRoller().getFileManager().getUploadDir();
-                    File f = new File(dir + website.getHandle() +
-                                      "/" + form.getImportFileName());
+                    FileManager fMgr = RollerFactory.getRoller().getFileManager();
+                    File f = fMgr.getFile(website.getHandle(), form.getImportFileName());
 
                     //ArchiveParser archiveParser =
                         //new ArchiveParser(RollerFactory.getRoller(), rreq.getWebsite(), f);
@@ -164,11 +164,6 @@
     private void getXmlFiles(ActionForm actionForm, RollerRequest rreq)
     {
 		String dir = null;
-                try {
-                    RollerFactory.getRoller().getFileManager().getUploadDir();
-                } catch(RollerException re) {
-                    // shouldn't happen
-                }
                 
 		File d = new File(dir + rreq.getWebsite().getHandle());
 		ArrayList xmlFiles = new ArrayList();

Modified: incubator/roller/trunk/src/org/apache/roller/ui/authoring/struts/actions/UploadFileFormAction.java
URL: http://svn.apache.org/viewvc/incubator/roller/trunk/src/org/apache/roller/ui/authoring/struts/actions/UploadFileFormAction.java?view=diff&rev=451071&r1=451070&r2=451071
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/ui/authoring/struts/actions/UploadFileFormAction.java (original)
+++ incubator/roller/trunk/src/org/apache/roller/ui/authoring/struts/actions/UploadFileFormAction.java Thu Sep 28 17:31:37 2006
@@ -19,6 +19,7 @@
 package org.apache.roller.ui.authoring.struts.actions;
 
 import java.io.File;
+import java.io.FileNotFoundException;
 import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -151,15 +152,11 @@
                         
                         fileSize = files[i].getFileSize();
                         
-                        //retrieve the file data
-                        if (fmgr.canSave(website.getHandle(), fileName,
-                                files[i].getContentType(), fileSize, rollerMessages)) {
-                            InputStream stream = files[i].getInputStream();
-                            fmgr.saveFile(website.getHandle(), fileName,
-                                    files[i].getContentType(), fileSize, stream);
-                            lastUploads.add(fileName);
-                        }
-                        
+                        InputStream stream = files[i].getInputStream();
+                        fmgr.saveFile(website.getHandle(), fileName,
+                                files[i].getContentType(), fileSize, stream);
+                        lastUploads.add(fileName);
+
                         //destroy the temporary file created
                         files[i].destroy();
                     }
@@ -176,8 +173,7 @@
             pageModel.setWebsite(website);
             
             RollerContext rctx = RollerContext.getRollerContext();
-            String baseURL = RollerRuntimeConfig.getAbsoluteContextURL();
-            String resourcesBaseURL = baseURL + fmgr.getUploadUrl() + "/" + website.getHandle();
+            String resourcesBaseURL = URLUtilities.getWeblogResourceURL(website, "", true);
             Iterator uploads = lastUploads.iterator();
             if (uploads.hasNext()) {
                 messages.add(ActionMessages.GLOBAL_MESSAGE,
@@ -306,7 +302,6 @@
             PropertiesManager pmgr = roller.getPropertiesManager();
             FileManager fmgr = roller.getFileManager();
             
-            String dir = fmgr.getUploadDir();
             resourcesBaseURL = URLUtilities.getWeblogResourceURL(weblog, "", false);
             
             RollerRequest rreq = RollerRequest.getRollerRequest(req);
@@ -318,13 +313,14 @@
             uploadEnabled = RollerRuntimeConfig.getBooleanProperty("uploads.enabled");
             
             files = new ArrayList();
-            File[] rawFiles = fmgr.getFiles(weblog.getHandle());
+            File[] rawFiles = fmgr.getFiles(weblog.getHandle(), null);
             for (int i=0; i<rawFiles.length; i++) {
                 files.add(new FileBean(rawFiles[i]));
                 totalSize += rawFiles[i].length();
             }
             Collections.sort(files, new FileBeanNameComparator());
         }
+        
         public boolean isUploadEnabled() {
             return uploadEnabled;
         }

Modified: incubator/roller/trunk/src/org/apache/roller/ui/core/RollerContext.java
URL: http://svn.apache.org/viewvc/incubator/roller/trunk/src/org/apache/roller/ui/core/RollerContext.java?view=diff&rev=451071&r1=451070&r2=451071
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/ui/core/RollerContext.java (original)
+++ incubator/roller/trunk/src/org/apache/roller/ui/core/RollerContext.java Thu Sep 28 17:31:37 2006
@@ -155,6 +155,16 @@
         // is set to ${webapp.context}
         RollerConfig.setUploadsDir(ctxPath);
         
+        // try setting the themes path to <context>/themes
+        // NOTE: this should go away at some point
+        // we leave it here for now to allow users to keep using
+        // themes in their webapp context, but this is a bad idea
+        //
+        // also, the RollerConfig.setThemesDir() method is smart
+        // enough to disregard this call unless the themes.dir
+        // is set to ${webapp.context}
+        RollerConfig.setThemesDir(mContext.getRealPath("/")+File.separator+"themes");
+        
         // set the roller context real path in RollerConfig
         // NOTE: it seems that a few backend classes do actually need
         //       to know what the real path to the roller context is,

Modified: incubator/roller/trunk/src/org/apache/roller/ui/rendering/servlets/ResourceServlet.java
URL: http://svn.apache.org/viewvc/incubator/roller/trunk/src/org/apache/roller/ui/rendering/servlets/ResourceServlet.java?view=diff&rev=451071&r1=451070&r2=451071
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/ui/rendering/servlets/ResourceServlet.java (original)
+++ incubator/roller/trunk/src/org/apache/roller/ui/rendering/servlets/ResourceServlet.java Thu Sep 28 17:31:37 2006
@@ -20,10 +20,10 @@
 
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.net.URLDecoder;
 import javax.servlet.ServletConfig;
 import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
@@ -32,14 +32,21 @@
 import javax.servlet.http.HttpServletResponse;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+import org.apache.roller.RollerException;
+import org.apache.roller.ThemeNotFoundException;
+import org.apache.roller.model.FileManager;
 import org.apache.roller.model.RollerFactory;
+import org.apache.roller.model.ThemeManager;
+import org.apache.roller.pojos.Theme;
+import org.apache.roller.pojos.WebsiteData;
 import org.apache.roller.ui.rendering.util.ModDateHeaderUtil;
+import org.apache.roller.ui.rendering.util.WeblogResourceRequest;
 
 
 /**
- * Resources servlet.  Acts as a gateway to files uploaded by users.
+ * Serves files uploaded by users as well as static resources in shared themes.
  *
- * Since we keep uploaded resources in a location outside of the webapp
+ * Since we keep resources in a location outside of the webapp
  * context we need a way to serve them up.  This servlet assumes that
  * resources are stored on a filesystem in the "uploads.dir" directory.
  *
@@ -49,8 +56,7 @@
 public class ResourceServlet extends HttpServlet {
 
     private static Log log = LogFactory.getLog(ResourceServlet.class);
-
-    private String upload_dir = null;
+    
     private ServletContext context = null;
 
 
@@ -59,16 +65,8 @@
         super.init(config);
 
         log.info("Initializing ResourceServlet");
-
+        
         this.context = config.getServletContext();
-
-        try {
-            this.upload_dir = RollerFactory.getRoller().getFileManager().getUploadDir();
-            log.debug("upload dir is ["+this.upload_dir+"]");
-        } catch(Exception e) {
-            log.error(e);
-        }
-
     }
 
 
@@ -77,68 +75,98 @@
      */
     public void doGet(HttpServletRequest request, HttpServletResponse response)
             throws ServletException, IOException {
-
+        
+        WebsiteData weblog = null;
         String context = request.getContextPath();
         String servlet = request.getServletPath();
         String reqURI = request.getRequestURI();
+        
+        WeblogResourceRequest resourceRequest = null;
+        try {
+            // parse the incoming request and extract the relevant data
+            resourceRequest = new WeblogResourceRequest(request);
 
-        // URL decoding
-
-        // Fix for ROL-1065: even though a + should mean space in a URL, folks 
-        // who upload files with plus signs expect them to work without 
-        // escaping. This is essentially what other systems do (e.g. JIRA) to 
-        // enable this.
-        reqURI = reqURI.replaceAll("\\+", "%2B");
-
-        // now we really decode the URL
-        reqURI = URLDecoder.decode(reqURI, "UTF-8");
-
-        // calculate the path of the requested resource
-        // we expect ... /<context>/<servlet>/path/to/resource
-        String reqResource = reqURI.substring(servlet.length() + context.length());
-
-        // now we can formulate the *real* path to the resource on the filesystem
-        String resource_path = this.upload_dir + reqResource;
-        File resource = new File(resource_path);
-
-        log.debug("Resource requested ["+reqURI+"]");
-        log.debug("Real path is ["+resource.getAbsolutePath()+"]");
+            weblog = resourceRequest.getWeblog();
+            if(weblog == null) {
+                throw new RollerException("unable to lookup weblog: "+
+                        resourceRequest.getWeblogHandle());
+            }
 
-        // do a quick check to make sure the resource exits, otherwise 404
-        if(!resource.exists() || !resource.canRead() || resource.isDirectory()) {
+        } catch(Exception e) {
+            // invalid resource request or weblog doesn't exist
+            log.debug("error creating weblog resource request", e);
             response.sendError(HttpServletResponse.SC_NOT_FOUND);
             return;
         }
-
-        // make sure someone isn't trying to sneek outside the uploads dir
-        File uploadDir = new File(this.upload_dir);
-        if(!resource.getCanonicalPath().startsWith(uploadDir.getCanonicalPath())) {
-            response.sendError(HttpServletResponse.SC_NOT_FOUND);
-            return;
+        
+        log.debug("Resource requested ["+resourceRequest.getResourcePath()+"]");
+        
+        File resource = null;
+        
+        // first see if resource comes from weblog's shared theme
+        if(!Theme.CUSTOM.equals(weblog.getEditorTheme())) {
+            try {
+                ThemeManager themeMgr = RollerFactory.getRoller().getThemeManager();
+                Theme weblogTheme = themeMgr.getTheme(weblog.getEditorTheme());
+                resource = weblogTheme.getResource(resourceRequest.getResourcePath());
+            } catch (Exception ex) {
+                // hmmm, some kind of error getting theme.  that's an error.
+                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+                return;
+            }
         }
-
+        
+        // if not from theme then see if resource is in weblog's upload dir
+        if(resource == null) {
+            try {
+                FileManager fileMgr = RollerFactory.getRoller().getFileManager();
+                resource = fileMgr.getFile(weblog.getHandle(), resourceRequest.getResourcePath());
+            } catch (Exception ex) {
+                // still not found? then we don't have it, 404.
+                log.debug("Unable to get resource", ex);
+                response.sendError(HttpServletResponse.SC_NOT_FOUND);
+                return;
+            }
+        }
+        
+        log.debug("Real path is ["+resource.getAbsolutePath()+"]");
+        
         // Respond with 304 Not Modified if it is not modified.
-        if (ModDateHeaderUtil.respondIfNotModified(request,response, resource.lastModified())) {
+        if (ModDateHeaderUtil.respondIfNotModified(request, response, resource.lastModified())) {
             return;
+        } else {
+            // set last-modified date
+            ModDateHeaderUtil.setLastModifiedHeader(response, resource.lastModified());
         }
-
-        // set last-modified date
-        ModDateHeaderUtil.setLastModifiedHeader(response,resource.lastModified());
+        
 
         // set the content type based on whatever is in our web.xml mime defs
         response.setContentType(this.context.getMimeType(resource.getAbsolutePath()));
+        
+        OutputStream out = null;
+        InputStream resource_file = null;
+        try {
+            // ok, lets serve up the file
+            byte[] buf = new byte[8192];
+            int length = 0;
+            out = response.getOutputStream();
+            resource_file = new FileInputStream(resource);
+            while((length = resource_file.read(buf)) > 0) {
+                out.write(buf, 0, length);
+            }
+            
+            // cleanup
+            out.close();
+            resource_file.close();
+            
+        } catch (Exception ex) {
+            log.error("Error writing resource file", ex);
+            if(!response.isCommitted()) {
+                response.reset();
+                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+            }
+        }
 
-        // ok, lets serve up the file
-        byte[] buf = new byte[8192];
-        int length = 0;
-        OutputStream out = response.getOutputStream();
-        InputStream resource_file = new FileInputStream(resource);
-        while((length = resource_file.read(buf)) > 0)
-            out.write(buf, 0, length);
-
-        // cleanup
-        out.close();
-        resource_file.close();
     }
 
 }

Added: incubator/roller/trunk/src/org/apache/roller/ui/rendering/util/WeblogResourceRequest.java
URL: http://svn.apache.org/viewvc/incubator/roller/trunk/src/org/apache/roller/ui/rendering/util/WeblogResourceRequest.java?view=auto&rev=451071
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/ui/rendering/util/WeblogResourceRequest.java (added)
+++ incubator/roller/trunk/src/org/apache/roller/ui/rendering/util/WeblogResourceRequest.java Thu Sep 28 17:31:37 2006
@@ -0,0 +1,109 @@
+/*
+ * 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.ui.rendering.util;
+
+import javax.servlet.http.HttpServletRequest;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.roller.util.URLUtilities;
+
+
+/**
+ * Represents a request for a weblog resource file.
+ *
+ * /roller-ui/rendering/resources/*
+ */
+public class WeblogResourceRequest extends WeblogRequest {
+    
+    private static Log log = LogFactory.getLog(WeblogResourceRequest.class);
+    
+    private static final String RESOURCE_SERVLET = "/roller-ui/rendering/resources";
+    
+    // lightweight attributes
+    private String resourcePath = null;
+    
+    
+    public WeblogResourceRequest() {}
+    
+    
+    /**
+     * Construct the WeblogResourceRequest by parsing the incoming url
+     */
+    public WeblogResourceRequest(HttpServletRequest request) 
+            throws InvalidRequestException {
+        
+        // let our parent take care of their business first
+        // parent determines weblog handle and locale if specified
+        super(request);
+        
+        String servlet = request.getServletPath();
+        
+        // we only want the path info left over from after our parents parsing
+        String pathInfo = this.getPathInfo();
+        
+        // parse the request object and figure out what we've got
+        log.debug("parsing path "+pathInfo);
+        
+        // was this request bound for the resource servlet?
+        if(servlet == null || !RESOURCE_SERVLET.equals(servlet)) {
+            throw new InvalidRequestException("not a weblog resource request, "+
+                    request.getRequestURL());
+        }
+        
+        
+        /* 
+         * any path is okay ...
+         *
+         * /<path>/<to>/<resource>
+         */
+        if(pathInfo != null && pathInfo.trim().length() > 1) {
+            
+            this.resourcePath = pathInfo;
+            if(pathInfo.startsWith("/")) {
+                this.resourcePath = pathInfo.substring(1);
+            }
+            
+            // Fix for ROL-1065: even though a + should mean space in a URL, folks
+            // who upload files with plus signs expect them to work without
+            // escaping. This is essentially what other systems do (e.g. JIRA) to
+            // enable this.
+            this.resourcePath = this.resourcePath.replaceAll("\\+", "%2B");
+            
+            // now we really decode the URL
+            this.resourcePath = URLUtilities.decode(this.resourcePath);
+        
+        } else {
+            throw new InvalidRequestException("invalid resource path info, "+
+                    request.getRequestURL());
+        }
+        
+        if(log.isDebugEnabled()) {
+            log.debug("resourcePath = "+this.resourcePath);
+        }
+    }
+
+    public String getResourcePath() {
+        return resourcePath;
+    }
+
+    public void setResourcePath(String resourcePath) {
+        this.resourcePath = resourcePath;
+    }
+    
+}

Modified: incubator/roller/trunk/src/org/apache/roller/ui/rendering/velocity/deprecated/ContextLoader.java
URL: http://svn.apache.org/viewvc/incubator/roller/trunk/src/org/apache/roller/ui/rendering/velocity/deprecated/ContextLoader.java?view=diff&rev=451071&r1=451070&r2=451071
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/ui/rendering/velocity/deprecated/ContextLoader.java (original)
+++ incubator/roller/trunk/src/org/apache/roller/ui/rendering/velocity/deprecated/ContextLoader.java Thu Sep 28 17:31:37 2006
@@ -441,12 +441,8 @@
      */
     private static String figureResourcePath() {
         
-        String uploadurl = null;
-        try {
-            uploadurl = RollerFactory.getRoller().getFileManager().getUploadUrl();
-        } catch(Exception e) {}
-        
-        return uploadurl;
+        // legacy junk.  this no longer makes any sense as of 3.0, but oh well
+        return "/resources";
     }
     
     

Modified: incubator/roller/trunk/src/org/apache/roller/webservices/atomprotocol/RollerAtomHandler.java
URL: http://svn.apache.org/viewvc/incubator/roller/trunk/src/org/apache/roller/webservices/atomprotocol/RollerAtomHandler.java?view=diff&rev=451071&r1=451070&r2=451071
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/webservices/atomprotocol/RollerAtomHandler.java (original)
+++ incubator/roller/trunk/src/org/apache/roller/webservices/atomprotocol/RollerAtomHandler.java Thu Sep 28 17:31:37 2006
@@ -360,7 +360,7 @@
                     "ERROR: cannot find specified weblog");
             }
             FileManager fmgr = mRoller.getFileManager();
-            File[] files = fmgr.getFiles(website.getHandle());
+            File[] files = fmgr.getFiles(website.getHandle(), null);
                         
             if (canView(website)) {
                 Feed feed = new Feed();
@@ -502,10 +502,13 @@
                     String handle = pathInfo[0];
                     WebsiteData website = 
                         mRoller.getUserManager().getWebsiteByHandle(handle);
-                    String uploadPath = 
-                        RollerFactory.getRoller().getFileManager().getUploadUrl();
+                    
+                    // TODO: this may have broken with 3.0, but i don't know
+                    // how it's supposed to work so i can't really fix it
+                    // it's unlikely this was working properly before because
+                    // it probably should have been using FileManager.getUploadsDir()
                     File resource = 
-                        new File(uploadPath + File.separator + fileName);
+                        new File("/resources" + File.separator + fileName);
                     return createAtomResourceEntry(website, resource);
                 }
             }
@@ -637,16 +640,13 @@
                     Utilities.copyInputToOutput(is, fos);
                     fos.close();
 
-                    // If save is allowed by Roller system-wide policies
-                    if (fmgr.canSave(website.getHandle(), fileName, contentType, tempFile.length(), msgs)) {
-                        // Then save the file
-                        FileInputStream fis = new FileInputStream(tempFile);
-                        fmgr.saveFile(website.getHandle(), fileName, contentType, tempFile.length(), fis);
-                        fis.close();
-
-                        File resource = new File(fmgr.getUploadDir() + File.separator + fileName);
-                        return createAtomResourceEntry(website, resource);
-                    }
+                    // Try saving file
+                    FileInputStream fis = new FileInputStream(tempFile);
+                    fmgr.saveFile(website.getHandle(), fileName, contentType, tempFile.length(), fis);
+                    fis.close();
+                    
+                    File resource = fmgr.getFile(website.getHandle(), fileName);
+                    return createAtomResourceEntry(website, resource);
 
                 } catch (IOException e) {
                     String msg = "ERROR reading posted file";

Modified: incubator/roller/trunk/src/org/apache/roller/webservices/xmlrpc/MetaWeblogAPIHandler.java
URL: http://svn.apache.org/viewvc/incubator/roller/trunk/src/org/apache/roller/webservices/xmlrpc/MetaWeblogAPIHandler.java?view=diff&rev=451071&r1=451070&r2=451071
==============================================================================
--- incubator/roller/trunk/src/org/apache/roller/webservices/xmlrpc/MetaWeblogAPIHandler.java (original)
+++ incubator/roller/trunk/src/org/apache/roller/webservices/xmlrpc/MetaWeblogAPIHandler.java Thu Sep 28 17:31:37 2006
@@ -357,22 +357,15 @@
             FileManager fmgr = roller.getFileManager();
             RollerMessages msgs = new RollerMessages();
             
-            // If save is allowed by Roller system-wide policies
-            if (fmgr.canSave(website.getHandle(), name, type, bits.length, msgs)) {
-                // Then save the file
-                fmgr.saveFile(website.getHandle(), name, type, bits.length, new ByteArrayInputStream(bits));
-                
-                // TODO: build URL to uploaded file should be done in FileManager
-                String uploadPath = RollerFactory.getRoller().getFileManager().getUploadUrl();
-                uploadPath += "/" + website.getHandle() + "/" + name;
-                String fileLink = URLUtilities.getWeblogResourceURL(website, name, true);
-                
-                Hashtable returnStruct = new Hashtable(1);
-                returnStruct.put("url", fileLink);
-                return returnStruct;
-            }
-            throw new XmlRpcException(UPLOAD_DENIED_EXCEPTION,
-                    "File upload denied because:" + msgs.toString());
+            // Try to save file
+            fmgr.saveFile(website.getHandle(), name, type, bits.length, new ByteArrayInputStream(bits));
+            
+            String fileLink = URLUtilities.getWeblogResourceURL(website, name, true);
+            
+            Hashtable returnStruct = new Hashtable(1);
+            returnStruct.put("url", fileLink);
+            return returnStruct;
+            
         } catch (RollerException e) {
             String msg = "ERROR in MetaWeblogAPIHandler.newMediaObject";
             mLogger.error(msg,e);

Modified: incubator/roller/trunk/tests/org/apache/roller/business/FileManagerTest.java
URL: http://svn.apache.org/viewvc/incubator/roller/trunk/tests/org/apache/roller/business/FileManagerTest.java?view=diff&rev=451071&r1=451070&r2=451071
==============================================================================
--- incubator/roller/trunk/tests/org/apache/roller/business/FileManagerTest.java (original)
+++ incubator/roller/trunk/tests/org/apache/roller/business/FileManagerTest.java Thu Sep 28 17:31:37 2006
@@ -25,8 +25,11 @@
 import junit.framework.TestSuite;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+import org.apache.roller.RollerException;
 import org.apache.roller.TestUtils;
+import org.apache.roller.model.FilePathException;
 import org.apache.roller.model.FileManager;
+import org.apache.roller.model.FileNotFoundException;
 import org.apache.roller.model.PropertiesManager;
 import org.apache.roller.model.Roller;
 import org.apache.roller.model.RollerFactory;
@@ -81,25 +84,7 @@
     }
     
     
-    public void testCanSave() throws Exception {
-        
-        // update roller properties to prepare for test
-        PropertiesManager pmgr = RollerFactory.getRoller().getPropertiesManager();
-        Map config = pmgr.getProperties();
-        ((RollerPropertyData)config.get("uploads.enabled")).setValue("false");
-        ((RollerPropertyData)config.get("uploads.types.forbid")).setValue("gif");
-        ((RollerPropertyData)config.get("uploads.dir.maxsize")).setValue("1.00");
-        pmgr.saveProperties(config);
-        TestUtils.endSession(true);
-        
-        // test quota functionality
-        FileManager fmgr = RollerFactory.getRoller().getFileManager();
-        RollerMessages msgs = new RollerMessages();
-        assertFalse(fmgr.canSave(testWeblog.getHandle(), "test.gif", "text/plain", 2500000, msgs));
-    }
-    
-    
-    public void testSave() throws Exception {
+    public void testFileCRUD() throws Exception {
         
         // update roller properties to prepare for test
         PropertiesManager pmgr = RollerFactory.getRoller().getPropertiesManager();
@@ -113,21 +98,117 @@
         /* NOTE: upload dir for unit tests is set in
                roller/personal/testing/roller-custom.properties */
         FileManager fmgr = RollerFactory.getRoller().getFileManager();
-        RollerMessages msgs = new RollerMessages();
+        
+        // we should be starting with 0 files
+        assertEquals(0, fmgr.getFiles(testWeblog.getHandle(), null).length);
+        
+        // create a directory
+        fmgr.createDirectory(testWeblog.getHandle(), "subdir");
+        
+        // make sure directory was created
+        assertEquals(1, fmgr.getFiles(testWeblog.getHandle(), null).length);
         
         // store a file
         InputStream is = getClass().getResourceAsStream("/bookmarks.opml");
         fmgr.saveFile(testWeblog.getHandle(), "bookmarks.opml", "text/plain", 1545, is);
         
         // make sure file was stored successfully
-        assertEquals(1, fmgr.getFiles(testWeblog.getHandle()).length);
+        assertEquals(2, fmgr.getFiles(testWeblog.getHandle(), null).length);
+        
+        // store a file into a subdirectory
+        is = getClass().getResourceAsStream("/bookmarks.opml");
+        fmgr.saveFile(testWeblog.getHandle(), "subdir/bookmarks.opml", "text/plain", 1545, is);
         
-        // delete a file
+        // make sure file was stored successfully
+        assertEquals(1, fmgr.getFiles(testWeblog.getHandle(), "subdir").length);
+        
+        // delete files and dirs
         fmgr.deleteFile(testWeblog.getHandle(), "bookmarks.opml");
+        fmgr.deleteFile(testWeblog.getHandle(), "subdir/bookmarks.opml");
+        fmgr.deleteFile(testWeblog.getHandle(), "subdir");
         
         // make sure delete was successful
         Thread.sleep(2000);
-        assertEquals(0, fmgr.getFiles(testWeblog.getHandle()).length);
+        assertEquals(0, fmgr.getFiles(testWeblog.getHandle(), null).length);
+    }
+    
+    
+    /**
+     * Test FileManager.saveFile() checks.
+     *
+     * This should test all conditions where a save should fail.
+     */
+    public void testCanSave() throws Exception {
+        
+        FileManager fmgr = RollerFactory.getRoller().getFileManager();
+        PropertiesManager pmgr = RollerFactory.getRoller().getPropertiesManager();
+        Map config = config = pmgr.getProperties();
+        ((RollerPropertyData)config.get("uploads.dir.maxsize")).setValue("1.00");
+        ((RollerPropertyData)config.get("uploads.types.forbid")).setValue("");
+        ((RollerPropertyData)config.get("uploads.types.allowed")).setValue("");
+        ((RollerPropertyData)config.get("uploads.enabled")).setValue("true");
+        pmgr.saveProperties(config);
+        TestUtils.endSession(true);
+        
+        Exception exception = null;
+        InputStream is = null;
+        
+        try {
+            // path check should fail
+            fmgr.saveFile(testWeblog.getHandle(), "some/path/foo.gif", "text/plain", 10, is);
+        } catch (Exception ex) {
+            log.error(ex);
+            exception = ex;
+        }
+        assertNotNull(exception);
+        exception = null;
+        
+        config = pmgr.getProperties();
+        ((RollerPropertyData)config.get("uploads.dir.maxsize")).setValue("1.00");
+        pmgr.saveProperties(config);
+        TestUtils.endSession(true);
+        
+        try {
+            // quota check should fail
+            fmgr.saveFile(testWeblog.getHandle(), "test.gif", "text/plain", 2500000, is);
+        } catch (Exception ex) {
+            log.error(ex);
+            exception = ex;
+        }
+        assertNotNull(exception);
+        exception = null;
+        
+        
+        config = pmgr.getProperties();
+        ((RollerPropertyData)config.get("uploads.types.forbid")).setValue("gif");
+        pmgr.saveProperties(config);
+        TestUtils.endSession(true);
+        
+        try {
+            // forbidden types check should fail
+            fmgr.saveFile(testWeblog.getHandle(), "test.gif", "text/plain", 10, is);
+        } catch (Exception ex) {
+            log.error(ex);
+            exception = ex;
+        }
+        assertNotNull(exception);
+        exception = null;
+        
+        
+        config = pmgr.getProperties();
+        ((RollerPropertyData)config.get("uploads.enabled")).setValue("false");
+        pmgr.saveProperties(config);
+        TestUtils.endSession(true);
+        
+        try {
+            // uploads disabled should fail
+            fmgr.saveFile(testWeblog.getHandle(), "test.gif", "text/plain", 10, is);
+        } catch (Exception ex) {
+            log.error(ex);
+            exception = ex;
+        }
+        assertNotNull(exception);
+        exception = null;
     }
     
 }

Modified: incubator/roller/trunk/web/WEB-INF/classes/roller.properties
URL: http://svn.apache.org/viewvc/incubator/roller/trunk/web/WEB-INF/classes/roller.properties?view=diff&rev=451071&r1=451070&r2=451071
==============================================================================
--- incubator/roller/trunk/web/WEB-INF/classes/roller.properties (original)
+++ incubator/roller/trunk/web/WEB-INF/classes/roller.properties Thu Sep 28 17:31:37 2006
@@ -64,6 +64,12 @@
 uploads.url=/resources
 
 #----------------------------------
+# Themes settings
+
+# The directory in which Roller will look for themes
+themes.dir=${webapp.context}
+
+#----------------------------------
 # Search index settings
 
 # Enables indexing of weblog entries and comments and enables search servlet