Integrating FCKEditor into Turbine

FCKEditor ( http://www.fckeditor.net ) is a great Javascript based editor. It can manage server repositories to store files for documents. Let us make a server side interface for this. Planning considerations:

  • there should be a lot of subrepository to store its data in server, for example, user´s introductions, news parts, etc.
  • All of entity can make subdirectories into own repository. It´s optional controlling by TR.properties
  • All of entity´s subdirectory can has a special directory named ""private"" to serve data for logged in users.
  • All of subrepository has quotas to reduce size of entity store.
  • File serving is made by Turbine to controlling access to files in repositories.

Making TurbineDirectory Service

there are 3 class of this package

DirectoryService.java

It is an interface for constants and method declarations.

package com.zamek.portal_zamek.services.directory;

import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;

import org.apache.commons.fileupload.FileItem;
import org.apache.turbine.services.Service;

import com.zamek.portal_zamek.PortalException;
import com.zamek.portal_zamek.util.FileInfo;

public interface DirectoryService extends Service {

    /**
     * The key under which this service is stored in TurbineServices.
     */
    String SERVICE_NAME = "DirectoryService";
    
    /**
     * repository part in url
     */
    String CTX_REPO = "repo";
    
    /**
     * id part in url
     */
    String CTX_ID = "id";
    
    /**
     * file part in url
     */
    String CTX_FILE = "file";
    
    /**
     * FileServ action part of link
     */
    String CTX_FILE_SERV = "FileServ";
    /**
     * repository path key in config
     */
    String REPOSITORY_KEY = "repository";
    
    /**
     * default base path
     */
    String DEFAULT_REPOSITORY = "resources";
    
    /**
     * default enable private
     */
    boolean DEFAULT_PRIVATE = false;

    /**
     * quota key in config
     */
    String QUOTA_KEY = "quota";
    
    /**
     * Private directory enable key in config
     */
    String KEY_PRIVATE_ENABLED = "private.enabled";
    
    /**
     * create directory enabled key in config
     */
    String KEY_CREATE_DIRECTORY_ENABLED = "create-directory.enabled";
    
    /**
     * quota key in config
     */
    String KEY_QUOTA = "quota";
    /**
     * default quota of each directory size
     */
    int DEFAULT_QUOTA = 1024000;
    
    /**
     * index of resourcebundle to quota exceeded
     */
    String ERR_QUOTA_EXCEEDED = "quota_exceede";
    
    String ERR_CANNOT_OPEN_FILE = "cannot_open_file";

    String MASK_PRIVATE = "/private";
    /**
     * Folder already exists error for FCKEditor
     */
    public final static int ERR_FOLDER_EXISTS =     101;
    
    /**
     * Invalid folder name error for FCKEditor
     */
    public final static int ERR_INVALID_FOLDER = 102;
    
    /**
     * You have no permissions to create the folder error for FCKEDitor
     */
    public final static int ERR_NO_PERMISSION = 103;
    
    /**
     * Unknown error creating folder for FCKEditor
     */
    public final static int ERR_UNKNOWN = 110;

    
    /**
     * getting list of folders in given folder for FCKEditor.
     * @param subRepo subsystem path in repository forexample user, news, etc.
     * @param id  entity id, root of entity
     * @param path url in own directory
     * @param includeMask  default mask is *, if it's null
     * @param excludeMask    default mask is null
     * @return list of directories in url directoy
     * @throws FileNotFoundException
     */
    public List<String> getFolders (final String subRepo, final String id,
                final String url, final String includeMask, final String excludeMask)
                throws FileNotFoundException;
    
    /**
     * getting list of files in given folder
     * @param subRepo subsystem path in repository forexample user, news, etc.
     * @param id entity id, root of entity
     * @param url path url in own directory
     * @param includeMask includeMask  default mask is *, if it's null
     * @param excludeMask excludeMask    default mask is null
     * @return list files in url directory
     * @throws FileNotFoundException
     */
    public List<FileInfo> getFiles(final String subRepo, final String id,
                final String url, final String includeMask, final String excludeMask)
                throws FileNotFoundException;
    
    /**
     * upload file to own directory using its original name
     * You can set quota in TR.properties in
     *     services.directoryservice.<subrepo>.quota=nn
     * quota is unlimited if it's 0
     * @param subRepo subsystem path in repository forexample user, news, etc.
     * @param id entity id, root of entity
     * @param path url in own directory
     * @param fileItem file to be upload
     * @return true if success
     * @throws PortalException
     * @throws Exception
     */
    public boolean uploadItemFile (final String subRepo, final String id,
                final String path, final FileItem fileItem)
                throws PortalException, Exception ;
    
    /**
     * upload file to own directory using newName
     * You can set quota in TR.properties in
     *     services.directoryservice.<subrepo>.quota=nn
     * quota is unlimited if it's 0
     * @param subRepo subsystem path in repository forexample user, news, etc.
     * @param id entity id, root of entity
     * @param path url in own directory
     * @param fileItem file to be upload
     * @param newName rename to this new name
     * @return true if success
     * @throws PortalException
     * @throws Exception
     */
    public boolean uploadItemFile (final String subRepo, final String id,
                final String path, final FileItem fileItem, final String newName)
                throws PortalException, Exception;

    /**
     * checking to has private directory
     * in TR.properties you can enable private directory:
     * services.directoryservice.<subrepo>.private.enabled=true
     * private directory is readable only for users, not to anonymous
     * @param subRepo subsystem path in repository forexample user, news, etc.
     * @param id entity id, root of entity
     * @return if private.enabled is true
     */
    public boolean hasPrivateDir (final String subRepo, final String id);
    
    /**
     * get a file by url from repository
     * @param subRepo subsystem path in repository forexample user, news, etc.
     * @param id entity id, root of entity
     * @param fileName name of file
     * @return bytes of file
     * @throws FileNotFoundException
     * @throws IOException
     * @throws PortalException
     */
    public ByteArrayOutputStream getFile (final String subRepo, final String id, final String fileName)
                throws FileNotFoundException, IOException, PortalException;
    
    /**
     * create a directory in repository if it's enabled by TR.properties
     * services.directoryservice.<subrepo>.create-directory.enabled
     * @param subRepo subsystem path in repository forexample user, news, etc.
     * @param id entity id, root of entity
     * @param folder folder to create
     * @return
     *               0: OK
     *             101: Folder already exists error for FCKEditor
     *                102: Invalid folder name error for FCKEditor
     *             103: You have no permissions to create the folder error for FCKEDitor
     *             110: Unknown error creating folder for FCKEditor
     * @throws FileNotFoundException
     */
    public int createDirectory (final String subRepo, final String id, final String folder)
                throws FileNotFoundException;
    
    /**
     * save string to repository to given name
     * @param subRepo subsystem path in repository forexample user, news, etc.
     * @param id id entity id, root of entity
     * @param path  url in own directory
     * @param fileName name of file to save
     * @param data data to save
     * @throws IOException
     */
    public void saveToFile(final String subRepo, final String id,
                final String path, final String fileName, final String data)
                throws IOException, PortalException;
    
    /**
     * checking given file to exists
     * @param subRepo subsystem path in repository forexample user, news, etc.
     * @param id id entity id, root of entity
     * @param path  url in own directory
     * @param fileName name of file to check
     * @return true if exists
     * @throws FileNotFoundException
     */
    public boolean isFileExists (final String subRepo, final String id,
                final String path, final String fileName)
                throws FileNotFoundException;

    /**
     * create directory of identity in subRepo
     */
    public boolean createIdDirectory (final String subRepo, final String id)
        throws FileNotFoundException;
    
    /**
     * getting quota of subRepo
     * @param subRepo
     * @return 0 if no quota
     */
    public long getQuota (final String subRepo);
    
    /**
     * getting real path of subrepo
     * @param subRepo
     * @return
     */
    public String getBasePath(final String subRepo);
    
    /**
     * getting enable create directory property
     * @param subRepo
     * @return
     */
    public boolean getCreateDirEnabled(final String subRepo);
    
    /**
     * getting enable private directory property
     * @param subRepo
     * @return
     */
    public boolean getPrivateDirEnabled (final String subRepo);
    
    /**
     * checking subrepo exists in TR.properties
     * @param subRepo
     * @return
     */
    public boolean isSubRepoEnabled(final String subRepo);
    
    /**
     * create a link for a file
     * @param subRepo
     * @param id
     * @param fileName
     * @return
     */
    public String getFileUrl(final String subRepo, final String id, final String fileName) ;
}

TurbineDirectoryService.java

This is the core of 'T'urbine'D'irectory service.

package com.zamek.portal_zamek.services.directory;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;

import org.apache.commons.configuration.Configuration;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.turbine.Turbine;
import org.apache.turbine.services.InitializationException;
import org.apache.turbine.services.TurbineBaseService;
import org.apache.turbine.services.localization.Localization;
import org.apache.turbine.util.uri.TemplateURI;

import com.zamek.portal_zamek.PortalException;
import com.zamek.portal_zamek.util.FileInfo;
import com.zamek.portal_zamek.util.FileUtils;

public class TurbineDirectoryService extends TurbineBaseService implements
        DirectoryService {

    private static Log log = LogFactory.getLog(TurbineDirectoryService.class);
    
    private final static int BUFFER_SIZE = 0x4000;
    
    private String repoPath = null;
    
    /*
     * list of disabled directory names
     */
    enum DisabledFolderNames
    {
        ROOT ("^/");
    
        private final String item;
    
        DisabledFolderNames(String item) { this.item = item; }
        String getItem () { return this.item; }
    }
    

    public TurbineDirectoryService() {
    
    }
    
    /**
     * initialisation. Get repository path from TR.prop
     */
    public void init () throws InitializationException
    {
        repoPath = getConfiguration().getString(
                DirectoryService.REPOSITORY_KEY,
                DirectoryService.DEFAULT_REPOSITORY);
    
        String testPath = Turbine.getRealPath(repoPath);
        File testDir = new File(testPath);
        if (!testDir.exists())
        {
            if (!testDir.mkdirs())
                throw new InitializationException("Couldn't create target directory!");
    
        }
        repoPath = testPath;
        getConfiguration().setProperty(DirectoryService.REPOSITORY_KEY, repoPath);
        log.debug("DirectoryService path is:" + repoPath);
        setInit(true);
    
    }

    /**
     * checking subrepo exists in TR.properties
     * @param subRepo
     * @return
     */
    public boolean isSubRepoEnabled(final String subRepo)
    {
        return StringUtils.isNumeric(getConfiguration().getString(subRepo + '.' + DirectoryService.KEY_QUOTA));
    }
    
    /**
     * getting list of folders in given folder for FCKEditor.
     * @param subRepo subsystem path in repository forexample user, news, etc.
     * @param id  entity id, root of entity
     * @param path url in own directory
     * @param includeMask  default mask is *, if it's null
     * @param excludeMask    default mask is null
     * @return list of directories in url directoy
     * @throws FileNotFoundException
     */
    public List<String> getFolders (final String subRepo, final String id, final String url,
                final String includeMask, final String excludeMask)
                throws FileNotFoundException
    {
        String basePath = getBasePath(subRepo, id);
    
        return basePath != null
                ? FileUtils.getDirectoryNames(basePath+File.separatorChar +
                        securityCheck (url), includeMask, excludeMask)
                : null;
    }
    
    /**
     * getting list of files in given folder for FCKEditor
     * @param subRepo subsystem path in repository forexample user, news, etc.
     * @param id entity id, root of entity
     * @param url path url in own directory
     * @param includeMask includeMask  default mask is *, if it's null
     * @param excludeMask excludeMask    default mask is null
     * @return list files in url directory
     * @throws FileNotFoundException
     */
    public List<FileInfo> getFiles(final String subRepo, final String id, final String url,
                final String includeMask, final String excludeMask)
                throws FileNotFoundException
    {
        String basePath = getBasePath(subRepo, id);
        return basePath != null
                ? FileUtils.getFileNames(basePath+File.separatorChar +
                        securityCheck (url), includeMask, excludeMask)
                : null;
    }
    
    /**
     * upload file to own directory using its original name
     * You can set quota in TR.properties in
     *     services.directoryservice.<subrepo>.quota=nn
     * quota is unlimited if it's 0
     * @param subRepo subsystem path in repository forexample user, news, etc.
     * @param id entity id, root of entity
     * @param path url in own directory
     * @param fileItem file to be upload
     * @return true if success
     * @throws PortalException
     * @throws Exception
     */
    public boolean uploadItemFile (final String subRepo, final String id,
                final String path, final FileItem fileItem)
                throws PortalException, Exception
    {
        return uploadItemFile(subRepo, id, path, fileItem, fileItem.getName());
    }
    
    /**
     * upload file to own directory using newName
     * You can set quota in TR.properties in
     *     services.directoryservice.<subrepo>.quota=nn
     * quota is unlimited if it's 0
     * @param subRepo subsystem path in repository forexample user, news, etc.
     * @param id entity id, root of entity
     * @param path url in own directory
     * @param fileItem file to be upload
     * @param newName rename to this new name
     * @return true if success
     * @throws PortalException
     * @throws Exception
     */
    public boolean uploadItemFile (final String subRepo, final String id,
                final String path, final FileItem fileItem, final String newName)
                throws PortalException, Exception
    {
        String basePath = getBasePath(subRepo, id);
        if (basePath == null)
            return false;
        Configuration conf = getConfiguration();
        int quota = conf.getInt(subRepo + '.' + DirectoryService.KEY_QUOTA, 0);
        // checking quotas
        if (quota > 0 &&
                ((getSize(basePath) + fileItem.getSize()) > quota))
            throw new PortalException (Localization.getString(ERR_QUOTA_EXCEEDED));
    
        try
        {
            FileUtils.uploadFile(fileItem, basePath+securityCheck (path), securityCheck(newName));
            return true;
        }
        catch (Exception e) {
            return false;
        }
    }
    
    /**
     * checking to has private directory
     * in TR.properties you can enable private directory:
     * services.directoryservice.<subrepo>.private.enabled=true
     * private directory is readable only for users, not to anonymous
     * @param subRepo subsystem path in repository forexample user, news, etc.
     * @param id entity id, root of entity
     * @return if private.enabled is true
     */
    public boolean hasPrivateDir (final String subRepo, final String id)
    {
        return getConfiguration().getBoolean(subRepo + '.' + DirectoryService.KEY_PRIVATE_ENABLED);
    }
    
    /**
     * get a file by url from repository
     * @param subRepo subsystem path in repository forexample user, news, etc.
     * @param id entity id, root of entity
     * @param fileName name of file
     * @return bytes of file
     * @throws FileNotFoundException
     * @throws IOException
     * @throws PortalException
     */
    public ByteArrayOutputStream getFile (final String subRepo, final String id,
                final String fileName)
                throws FileNotFoundException, IOException, PortalException
    {
        //getting basePath
        String basePath = getBasePath(subRepo, id);
        if (basePath == null)
            return null;
        File file = new File (basePath + File.separatorChar + securityCheck(fileName));
        if (file.isFile() && file.exists() && file.canRead())
        {
            FileInputStream fis = new FileInputStream(file);
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
    
            byte[] buffer = new byte[BUFFER_SIZE];
            int len = 0;
            for(len = fis.read(buffer, 0, BUFFER_SIZE);len != -1;len = fis.read(buffer, 0 , BUFFER_SIZE))
                bos.write(buffer, 0, len);
            return bos;
        }
        else
            throw new PortalException (String.format(Localization.getString(ERR_CANNOT_OPEN_FILE),fileName));
    }
    
    /**
     * create a directory in repository if it's enabled by TR.properties
     * services.directoryservice.<subrepo>.create-directory.enabled
     * @param subRepo subsystem path in repository forexample user, news, etc.
     * @param id entity id, root of entity
     * @param folder folder to create
     * @return
     *               0: OK
     *             101: Folder already exists error for FCKEditor
     *                102: Invalid folder name error for FCKEditor
     *             103: You have no permissions to create the folder error for FCKEDitor
     *             110: Unknown error creating folder for FCKEditor
     * @throws FileNotFoundException
     */
    public int createDirectory (final String subRepo, final String id, final String folder)
            throws FileNotFoundException
    {
        Configuration conf = getConfiguration();
        if (conf.getBoolean(subRepo+"."+DirectoryService.KEY_CREATE_DIRECTORY_ENABLED))
        {
    
            String basePath = getBasePath(subRepo, id);
    
            if (basePath == null || folder == null || folder.length() <= 0)
                return DirectoryService.ERR_UNKNOWN;
    
            // checking disabled folder names
            for (DisabledFolderNames dfn : DisabledFolderNames.values() )
                if (folder.matches(dfn.getItem()))
                        return ERR_INVALID_FOLDER;
    
            try
            {
                File newDir = new File (basePath + securityCheck(folder));
                if (newDir == null)
                    return ERR_UNKNOWN;
    
                if (newDir.exists())
                    return ERR_FOLDER_EXISTS;
    
                return newDir.mkdir() ? 0 : ERR_UNKNOWN;
            }
            catch(Exception e) {
                return ERR_INVALID_FOLDER;
            }
        }
        else
            return ERR_INVALID_FOLDER;
    }
    
    /**
     * save string to repository to given name
     * @param subRepo subsystem path in repository forexample user, news, etc.
     * @param id id entity id, root of entity
     * @param path  url in own directory
     * @param fileName name of file to save
     * @param data data to save
     * @throws IOException
     */
    public void saveToFile(final String subRepo, final String id,
                final String path, final String fileName, final String data)
                throws IOException, PortalException
    {
        String basePath = getBasePath(subRepo, id);
    
        if (basePath == null || fileName == null || fileName.length() == 0)
            return;

        int quota = getConfiguration().getInt(subRepo + '.' + DirectoryService.KEY_QUOTA, 0);
    
        // checking quotas
        if (quota > 0 &&
                ((getSize(basePath) + data.length()) > quota))
            throw new PortalException (Localization.getString(ERR_QUOTA_EXCEEDED));
    
        File file = new File (basePath + securityCheck(path) + File.separatorChar + securityCheck (fileName));
        PrintWriter out = new PrintWriter(new FileWriter(file));
        out.print(data);
        out.close();
    }
    
    /**
     * checking given file to exists
     * @param subRepo subsystem path in repository forexample user, news, etc.
     * @param id id entity id, root of entity
     * @param path  url in own directory
     * @param fileName name of file to check
     * @return true if exists
     * @throws FileNotFoundException
     */
    public boolean isFileExists (final String subRepo, final String id,
                final String path, final String fileName)
                throws FileNotFoundException
    {
        String basePath = getBasePath(subRepo, id);
        if (basePath == null)
            return false;
    
        File file = new File (basePath + securityCheck(path) + File.separatorChar + fileName);
        return file.exists();
    }

    public long getSize(final String basePath)
    {
        return FileUtils.getSize(basePath);
    }

    /**
     * create directory of identity in subRepo
     */
    public boolean createIdDirectory (final String subRepo, final String id)
        throws FileNotFoundException
    {
        return getBasePath(subRepo, id, true) != null;
    }
    
    
    private String getBasePath(final String subRepo, final String id, boolean createIfNotExists)
    {
        String result = new StringBuffer(repoPath).append(File.separatorChar).append(subRepo).
        append(File.separatorChar).append(id).append(File.separatorChar).toString();
        File dir = new File(result);
        if (dir.exists() && dir.isDirectory())
            return result;
        if (createIfNotExists)
            dir.mkdirs();
        return result;
    }
    
    
    private String getBasePath(final String subRepo, final String id) throws FileNotFoundException
    {
        return getBasePath (subRepo, id, false);
    }
    
    /**
     * checking url security, removing all of ../ ./
     * @param url
     * @return
     */
    private String securityCheck(final String url) {
        return url.compareTo("/") == 0
                ? ""
                : url.replaceAll("\\.+\\/", "");
    }

    /**
     * getting real path of subrepo
     * @param subRepo
     * @return
     */
    public String getBasePath(String subRepo) {
        return new StringBuffer(repoPath).append(File.separatorChar).append(subRepo).toString();
    }

    /**
     * getting enable create directory property
     * @param subRepo
     * @return
     */
    public boolean getCreateDirEnabled(String subRepo) {
        return getConfiguration().getBoolean(subRepo + '.' + DirectoryService.KEY_CREATE_DIRECTORY_ENABLED);
    
    }

    /**
     * getting enable private directory property
     * @param subRepo
     * @return
     */
    public boolean getPrivateDirEnabled(String subRepo) {
        return getConfiguration().getBoolean(subRepo+ '.' + DirectoryService.KEY_PRIVATE_ENABLED);
    }

    /**
     * getting quota of subRepo
     * @param subRepo
     * @return 0 if no quota
     */
    public long getQuota(String subRepo) {
        return getConfiguration().getLong(subRepo+ '.' + DirectoryService.KEY_QUOTA);
    }
    
    public String getFileUrl(final String subRepo, final String id, final String fileName)
    {
        if (! isSubRepoEnabled(subRepo))
            return null;
        TemplateURI uri = new TemplateURI();
        uri.setScreen(CTX_FILE_SERV);
        uri.addPathInfo(CTX_REPO, subRepo);
        uri.addPathInfo(CTX_ID, id);
        uri.addPathInfo(CTX_FILE, fileName.replace('/', ','));
        return uri.toString();
    }
    
}

TurbineDirectory.java

package com.zamek.portal_zamek.services.directory;

import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;

import org.apache.commons.fileupload.FileItem;
import org.apache.turbine.services.TurbineServices;

import com.zamek.portal_zamek.PortalException;
import com.zamek.portal_zamek.util.FileInfo;


public class TurbineDirectory {

    public static DirectoryService getService() {
        return (DirectoryService) TurbineServices.getInstance().
                getService(DirectoryService.SERVICE_NAME);
    }
    
    public static boolean isAvailable()
    {
        try
        {
            DirectoryService directory = getService();
            return directory != null;
        }
        catch (Exception e) {
            return false;
        }
    }
    
    /**
     * checking subrepo enabled in TR.properties
     * @param subRepo
     * @return
     */
    public static boolean isSubRepoEnabled (final String subRepo)
    {
        return getService().isSubRepoEnabled(subRepo);
    }
    
    /**
     * getting list of folders in given folder for FCKEditor.
     * @param subRepo subsystem path in repository forexample user, news, etc.
     * @param id  entity id, root of entity
     * @param path url in own directory
     * @param includeMask  default mask is *, if it's null
     * @param excludeMask    default mask is null
     * @return list of directories in url directoy
     * @throws FileNotFoundException
     */
    public static List<String> getFolders (final String subRepo, final String id,
                    final String url, final String includeMask, final String excludeMask)
                    throws FileNotFoundException
    {
        return getService().getFolders(subRepo, id, url, includeMask, excludeMask);
    }
    
    /**
     * getting list of files in given folder
     * @param subRepo subsystem path in repository forexample user, news, etc.
     * @param id entity id, root of entity
     * @param url path url in own directory
     * @param includeMask includeMask  default mask is *, if it's null
     * @param excludeMask excludeMask    default mask is null
     * @return list files in url directory
     * @throws FileNotFoundException
     */
    public static List<FileInfo> getFiles(final String subRepo, final String id,
                    final String url, final String includeMask, final String excludeMask)
                    throws FileNotFoundException
    {
        return getService().getFiles(subRepo, id, url, includeMask, excludeMask);
    }
    
    /**
     * upload file to own directory using its original name
     * You can set quota in TR.properties in
     *     services.directoryservice.<subrepo>.quota=nn
     * quota is unlimited if it's 0
     * @param subRepo subsystem path in repository forexample user, news, etc.
     * @param id entity id, root of entity
     * @param path url in own directory
     * @param fileItem file to be uplode
     * @return true if success
     * @throws PortalException
     * @throws Exception
     */
    public static boolean uploadItemFile (final String subRepo, final String id,
                    final String path, final FileItem fileItem)
                    throws PortalException, Exception
    {
        return getService().uploadItemFile(subRepo, id, path, fileItem);
    }
    
    /**
     * upload file to own directory using newName
     * You can set quota in TR.properties in
     *     services.directoryservice.<subrepo>.quota=nn
     * quota is unlimited if it's 0
     * @param subRepo subsystem path in repository forexample user, news, etc.
     * @param id entity id, root of entity
     * @param path url in own directory
     * @param fileItem file to be uplode
     * @return true if success
     * @throws PortalException
     * @throws Exception
     */
    public static boolean uploadItemFile (final String subRepo, final String id,
                    final String path, final FileItem fileItem, final String newName)
                    throws PortalException, Exception
    {
        return getService().uploadItemFile(subRepo, id, path, fileItem, newName);
    }

    /**
     * checking to has private directory
     * in TR.properties you can enable private directory:
     * services.directoryservice.<subrepo>.private.enabled=true
     * private directory is readable only for users, not to anonymous
     * @param subRepo subsystem path in repository forexample user, news, etc.
     * @param id entity id, root of entity
     * @return if private.enabled is true
     */
    public static boolean hasPrivateDir (final String subRepo, final String id)
    {
        return getService().hasPrivateDir(subRepo, id);
    }
    
    /**
     * get a file by url from repository
     * @param subRepo subsystem path in repository forexample user, news, etc.
     * @param id entity id, root of entity
     * @param fileName name of file
     * @return bytes of file
     * @throws FileNotFoundException
     * @throws IOException
     * @throws PortalException
     */
    public static ByteArrayOutputStream getFile (final String subRepo, final String id, final String fileName)
                    throws FileNotFoundException, IOException, PortalException
    {
        return getService().getFile(subRepo, id, fileName);
    }
    
    /**
     * create a directory in repository if it's enabled by TR.properties
     * services.directoryservice.<subrepo>.create-directory.enabled
     * @param subRepo subsystem path in repository forexample user, news, etc.
     * @param id entity id, root of entity
     * @param folder folder to create
     * @return
     *               0: OK
     *             101: Folder already exists error for FCKEditor
     *                102: Invalid folder name error for FCKEditor
     *             103: You have no permissions to create the folder error for FCKEDitor
     *             110: Unknown error creating folder for FCKEditor
     * @throws FileNotFoundException
     */
    public static int createDirectory (final String subRepo, final String id, final String folder)
                    throws FileNotFoundException
    {
        return getService().createDirectory(subRepo, id, folder);
    }
    
    /**
     * save string to repository to given name
     * @param subRepo subsystem path in repository forexample user, news, etc.
     * @param id id entity id, root of entity
     * @param path  url in own directory
     * @param fileName name of file to save
     * @param data data to save
     * @throws IOException
     */
    public static void saveToFile(final String subRepo, final String id,
                    final String path, final String fileName, final String data)
                    throws IOException, PortalException
    {
        getService().saveToFile(subRepo, id, path, fileName, data);
    }
    
    /**
     * checking given file to exists
     * @param subRepo subsystem path in repository forexample user, news, etc.
     * @param id id entity id, root of entity
     * @param path  url in own directory
     * @param fileName name of file to check
     * @return true if exists
     * @throws FileNotFoundException
     */
    public static boolean isFileExists (final String subRepo, final String id,
                    final String path, final String fileName)
                    throws FileNotFoundException
    {
        return getService().isFileExists(subRepo, id, path, fileName);
    }

    
    public static boolean createIdDirectory(final String subRepo, final String id)
                    throws FileNotFoundException
    {
        return getService().createIdDirectory(subRepo, id);
    
    }
    
    /**
     * getting real path of subrepo
     * @param subRepo
     * @return
     */
    public static String getBasePath(String subRepo)
    {
        return getService().getBasePath(subRepo);
    }

    /**
     * getting enable create directory property
     * @param subRepo
     * @return
     */
    public static boolean getCreateDirEnabled(String subRepo)
    {
        return getService().getCreateDirEnabled(subRepo);
    }

    /**
     * getting enable private directory property
     * @param subRepo
     * @return
     */
    public static boolean getPrivateDirEnabled(String subRepo)
    {
        return getService().getPrivateDirEnabled(subRepo);
    }

    /**
     * getting quota of subRepo
     * @param subRepo
     * @return 0 if no quota
     */
    public long getQuota(String subRepo)
    {
        return getService().getQuota(subRepo);
    }
    
    /**
     * return a link for a file
     * @param subRepo
     * @param id
     * @param fileName
     * @return
     */
    public static String getFileUrl(final String subRepo, final String id, final String fileName)
    {
        return getService().getFileUrl(subRepo, id, fileName);
    }
}

setting in TR.properties

In TR.properties, you can make different settings to different subrepositories. for example in this settings has a user's repository and a news module repository

services.DirectoryService.classname=com.zamek.portal_zamek.services.directory.TurbineDirectoryService
services.DirectoryService.earlyInit=true
services.DirectoryService.repository=resources  # default repository root

# properties of resources/user, this is the user's own repositories
services.DirectoryService.user.quota=1024000        # 1Mb
services.DirectoryService.user.create-directory.enabled=true  # users can create subdirecories its own repository
services.DirectoryService.user.private.enabled=true  # users can make private directory to serve files only logged in users

# properties of news modul in resources/news 
services.DirectoryService.news.quota=100000    # ~100Kb
services.DirectoryService.news.create-directory.enabled=false  #news cannott create directories
services.DirectoryService.news.private.enabled=false     #news cannot create private directory

Serving FCKEditor

Now we have to make an action to serve fckeditor. This is a simple 'V'elocity'A'ction descendants. There are following commands from FCKEditor:

  • 'G'et'F'olders, need an xml response
  • 'G'et'F'olders'A'nd'F'iles need an xml response
  • 'C'reate'F'older need an xml response
  • 'F'ile'U'pload need a string response

In different modules you can override two of methods:

    boolean canCreateFolder() {
        data.getUser().hasLoggedIn() && TurbineDirectory.getCreateDirEnabled(repo);
    }

which is mean can create if user is has logged in and TR.properties enabled services.'D'irectory'S'ervice.<repo>.create-directory.enabled=true

    boolean canUpload () {
        return data.getUser().hasLoggedIn();
    }
you can override it for example return super.canUpload() && data.getAcl().hasRole(SOME_ROLE);

All of 'FC'KA'*_ction is here:

package com.zamek.portal_zamek.modules.actions;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.commons.fileupload.FileItem;
import org.apache.turbine.util.RunData;
import org.apache.turbine.util.parser.ParameterParser;
import org.apache.turbine.util.uri.TurbineURI;
import org.apache.velocity.context.Context;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import com.zamek.portal_zamek.ZamekConst;
import com.zamek.portal_zamek.services.directory.DirectoryService;
import com.zamek.portal_zamek.services.directory.TurbineDirectory;
import com.zamek.portal_zamek.util.FileInfo;


/**
 * 
 * @author zamek
 *  request: Command=GetFolders&Type=File&CurrentFolder=/Samples/Docs/
 *  response: 
 *  <?xml version="1.0" encoding="utf-8" ?>
 *    <Connector command="GetFolders" resourceType="File">
 *   <CurrentFolder path="/Samples/Docs/" url="/UserFiles/File/Samples/Docs/" />
 *   <Folders>
 *     <Folder name="Documents" />
 *     <Folder name="Files" />
 *     <Folder name="Other Files" />
 *     <Folder name="Related" />
 *   </Folders>
 * </Connector>
 * 
 *  request: Command=GetFoldersAndFiles&Type=File&CurrentFolder=/Samples/Docs/
 *  response:
 *  <?xml version="1.0" encoding="utf-8" ?>
 *  <Connector command="GetFoldersAndFiles" resourceType="File">
 *   <CurrentFolder path="/Samples/Docs/" url="/UserFiles/File/Samples/Docs/" />
 *   <Folders>
 *     <Folder name="Documents" />
 *     <Folder name="Files" />
 *     <Folder name="Other Files" />
 *     <Folder name="Related" />
 *   </Folders>
 *   <Files>
 *     <File name="XML Definition.doc" size="14" />
 *     <File name="Samples.txt" size="5" />
 *     <File name="Definition.txt" size="125" />
 *     <File name="External Resources.drw" size="840" />
 *     <File name="Todo.txt" size="2" />
 *   </Files>
 *  </Connector>
 *  
 *  request:Command=CreateFolder&Type=File&CurrentFolder=/Samples/Docs/&NewFolderName=FolderName
 *  response:
 *  <?xml version="1.0" encoding="utf-8" ?>
 *  <Connector command="CreateFolder" resourceType="File">
 *    <CurrentFolder path="/Samples/Docs/" url="/UserFiles/File/Samples/Docs/" />
 *    <Error number="0" />
 *  </Connector>
 *  
 *  request: Command=FileUpload&Type=File&CurrentFolder=/Samples/Docs/
 *  response: (Simple HTML):
 *  <script type="text/javascript">
 *       window.parent.frames['frmUpload'].OnUploadCompleted(0) ;
 *  </script>
 *  #OnUploadCompleted( 0 ) : no errors found on the upload process.
 *  #OnUploadCompleted( 201, 'FileName(1).ext' ) : the file has been uploaded successfully, but its name has been changed to "FileName(1).ext".
 *  #OnUploadCompleted( 202 ) : invalid file.
 *  
 *    Possible Error Numbers are:
 *       0 : No Errors Found. The folder has been created.
 *     101 : Folder already exists.
 *     102 : Invalid folder name.
 *     103 : You have no permissions to create the folder.
 *     110 : Unknown error creating folder.
 */

public class FckAction extends AbstractAction {
    private final static String LOGHEADER = "com.zamek.portal_zamek.modules.actions.";

    protected final static String CTX_COMMAND = "Command";
    protected final static String CTX_TYPE = "Type";
    protected final static String CTX_CURRENT_FOLDER = "CurrentFolder";
    protected final static String CTX_NEW_FOLDER = "NewFolderName";
    protected final static String CTX_NEW_FILE = "NewFile";
    protected final static String FT_FILE = "File";
    protected final static String FT_IMAGE = "Image";
    protected final static String FT_FLASH = "Flash";
    protected final static String FT_MEDIA = "Media";
    protected final static String CTX_FILE_SERV_ACTION = "FileServ";
    
    private final static String CMD_GET_FOLDERS = "GetFolders";
    private final static String CMD_GET_FOLDERS_AND_FILES = "GetFoldersAndFiles";
    private final static String CMD_CREATE_FOLDER = "CreateFolder";
    private final static String CMD_FILE_UPLOAD = "FileUpload";
    protected final static String XML_CONNECTOR = "Connector";
    protected final static String XML_COMMAND = "command";
    protected final static String XML_CURRENT_FOLDER = "CurrentFolder";
    protected final static String XML_PATH = "path";
    protected final static String XML_URL = "url";
    protected final static String XML_RESOURCE_TYPE = "resourceType";
    protected final static String XML_FOLDERS = "Folders";
    protected final static String XML_FOLDER = "Folder";
    protected final static String XML_FILES = "Files";
    protected final static String XML_FILE = "File";
    protected final static String XML_NAME = "name";
    protected final static String XML_SIZE = "size";
    protected final static String XML_ERROR = "Error";
    protected final static String XML_NUMBER = "Number";
    
    protected final static int KILO_BYTE= 1024;
    protected final static String CTX_RESPONSE="response";

    protected final static int ERR_FOLDER_EXISTS =     101; // : Folder already exists.
    protected final static int ERR_INVALID_FOLDER = 102; // : Invalid folder name.
    protected final static int ERR_NO_PERMISSION = 103;  // : You have no permissions to create the folder.
    protected final static int ERR_UNKNOWN = 110;          // : Unknown error creating folder.

    protected final static int RES_UPLOAD_INVALID = 202;
    protected final static int RES_UPLOAD_OK = 0;
    protected final static int RES_UPLOAD_NAME_CHANGED = 201;
    
    protected final static String UPLOAD_OK_RESULT =
        "<script type=\"text/javascript\">window.parent.frames['frmUpload'].OnUploadCompleted(0,\"%s\");</script>";
    protected final static String UPLOAD_ERROR_RESULT =
        "<script type=\"text/javascript\">window.parent.frames['frmUpload'].OnUploadCompleted(%d);</script>";
    
    protected String command = null,
                      type = null,
                      currentFolder = null,
                      scriptResult = null,
                      repo = null,
                      id = null;
    
    protected RunData data;
    
    protected Document document = null;
    protected File serverDir = null;
    protected Node rootNode = null;
    
    public void doPerform(RunData data, Context ctx) throws Exception {
        this.data = data;
        data.declareDirectResponse();
        data.setLayout(ZamekConst.DIRECT_RESPONSE_LAYOUT);
        processRequest(data);
    }

    private void processRequest(RunData data) throws ServletException, IOException {
        HttpServletResponse response = data.getResponse();
        try {
            ParameterParser pp = data.getParameters();
            command = pp.get(CTX_COMMAND);
            type =  pp.get(CTX_TYPE);
            currentFolder = pp.get(CTX_CURRENT_FOLDER);
            repo = pp.get(DirectoryService.CTX_REPO);
            id = pp.get(DirectoryService.CTX_ID);
    
            if (command == null || command.length() == 0 ||   // possible abuse
                repo == null || repo.length() == 0 ||
                id == null || id.length() == 0)
                return;
    
            initDocument();
    
            rootNode = createCommonXml();
            doCommand();
            if (rootNode == null) {  // result of upload is a javascipt code
                response.setContentType("text/html");
                response.getOutputStream().print(scriptResult);
                return;
            }
            response.setContentType("text/xml");
            document.getDocumentElement().normalize();
            try {
                System.setProperty ( "javax.xml.transform.TransformerFactory" ,
                        "org.apache.xalan.xsltc.trax.TransformerFactoryImpl");     
                TransformerFactory tFactory = TransformerFactory.newInstance();
                Transformer transformer = tFactory.newTransformer();
                DOMSource source = new DOMSource(document);
                StreamResult result = new StreamResult(new ByteArrayOutputStream ());
                transformer.transform(source, result);
                String xml = result.getOutputStream().toString();
                response.getOutputStream().print(xml);
            }
            catch (Exception e) {
                errorReport (data, LOGHEADER, e);
            }
            
        }
        catch (Exception e) {
            log.error(LOGHEADER+" error:"+e.getMessage());
        }    
    }
    
    protected void initDocument () throws ParserConfigurationException {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        document = builder.newDocument();
    }

    protected Node createCommonXml () {
           Element root = document.createElement(XML_CONNECTOR);
           document.appendChild(root);
           root.setAttribute(XML_COMMAND, command);
           root.setAttribute(XML_RESOURCE_TYPE, type);
    
           Element el = document.createElement(XML_CURRENT_FOLDER);
           el.setAttribute(XML_PATH, currentFolder);
    
        TemplateURI tu = new TemplateURI();
        tu.setScreen(CTX_FILE_SERV_ACTION);
        tu.addPathInfo(DirectoryService.CTX_REPO, repo); 
        tu.addPathInfo(DirectoryService.CTX_ID, id);
        String fn = currentFolder.replace('/', ',');
        tu.addPathInfo(DirectoryService.CTX_FILE, fn);
        el.setAttribute(XML_URL, tu.toString());
           root.appendChild(el);
           return root;
    }

    protected void doCommand() throws Exception {
        if (command.compareTo(CMD_GET_FOLDERS_AND_FILES) == 0) {
            getFoldersAndFiles (rootNode);
            return;
        }
        if (command.compareTo(CMD_GET_FOLDERS) == 0) {
            getFolders(rootNode);
            return;
        }
        if (command.compareTo(CMD_CREATE_FOLDER) == 0) {
            createFolder (rootNode);
            return;
        }
        if (command.compareTo(CMD_FILE_UPLOAD) == 0) {
            scriptResult = fileUpload();
            rootNode = null;
        }
    }
    
    protected String getFileTypeMask() {
        if (type.compareTo(FT_FILE) == 0)
            return null;
        if (type.compareTo(FT_IMAGE) == 0)
            return ".*\\.gif|.*\\.jpg|.*\\.jpeg|.*\\.png";
        if (type.compareTo(FT_FLASH) == 0)
            return ".*\\.swf";
        if (type.compareTo(FT_MEDIA) == 0)
            return ".*\\.vmw|.*\\.mov";
        return null;
    }

    protected void getFoldersAndFiles(Node root) throws Exception {
        getFolders(root);
        List<FileInfo> result = getFoldersAndFileList();
        Element files = document.createElement(XML_FILES);
        root.appendChild(files);
        if (result != null && result.size() > 0) {
            for (Iterator<FileInfo> it=result.iterator(); it.hasNext(); ) {
                Element file = document.createElement(XML_FILE);
                FileInfo fi = it.next();
                files.appendChild(fi.setElement(file, KILO_BYTE));
            }
        }
    }
    
    protected void getFolders(Node root) throws Exception {
        List<String> result = getFoldersList();
         Element folders = document.createElement(XML_FOLDERS);
        root.appendChild(folders);
        if (result != null && result.size() > 0) {
           for (Iterator<String> it=result.iterator(); it.hasNext(); ) {
               Element subDir = document.createElement(XML_FOLDER);
               subDir.setAttribute(XML_NAME, it.next());
               folders.appendChild(subDir);
           }
        }
    }
    
    protected List<FileInfo> getFoldersAndFileList() throws Exception
    {
        return TurbineDirectory.getFiles(repo, id, currentFolder, getIncludeMask(), getExcludeMask());
    }
    
    protected List<String> getFoldersList() throws Exception
    {
        return TurbineDirectory.getFolders(repo, id, currentFolder, getIncludeMask(), getExcludeMask());
    }
    
    protected String getBaseUrl(RunData data) throws Exception
    {
        return repo+'/'+id;
    }
    
    protected int createFolder(Node root) throws Exception
    {
        String newFolder = data.getParameters().get(CTX_NEW_FOLDER);
        int result = canCreateFolder() && newFolder != null
            ? TurbineDirectory.createDirectory(repo, id, newFolder)
            : DirectoryService.ERR_NO_PERMISSION;
    
        Element crf = document.createElement(XML_ERROR);
        crf.setAttribute(XML_ERROR, Integer.toString(result));
        root.appendChild(crf);
        return result;
    }
    
    protected String fileUpload() throws Exception
    {
        int result = RES_UPLOAD_INVALID;
        if (canUpload())
        {
            FileItem fItem = data.getParameters().getFileItem(CTX_NEW_FILE);
            if (fItem != null)
            {
                try
                {
                    if (TurbineDirectory.createIdDirectory(repo, id) &&
                        TurbineDirectory.uploadItemFile(repo, id, currentFolder, fItem))
                        return String.format(UPLOAD_OK_RESULT, fItem.getName());
                }
                catch (Exception e) {
                    log.error(LOGHEADER+"fileUpload error"+e.getMessage());
                }
            }
    
        }
        return String.format(UPLOAD_ERROR_RESULT, result);
    }
    
    /* overridable methods */

    /**
     * return rights to can create directory
     * RunData is readable by protected data variable
     */
    protected boolean canCreateFolder()
    {
        return data.getUser().hasLoggedIn() && TurbineDirectory.getCreateDirEnabled(repo);
    }
    
    /**
     * return rights to can upload
     * RunData is readable by protected data variable
     */
    protected boolean canUpload () {
        return data.getUser().hasLoggedIn();
    }
    
    /**
     * getFolders() an getFolderAndFileList() using to set including name mask
     * null is similar to *
     * default value is null
     * @return
     */
    protected String getIncludeMask()
    {
        return null;
    }
    
    /**
     * getFolders() an getFolderAndFileList() using to set excluding name mask
     * null is similar to nothing
     * default is null
     * @return
     */
    protected String getExcludeMask()
    {
        return null;
    }
    
}

serving files to requests

We need to make a screen module to serve file requests. It is a descendants of_* 'R'aw'S'creen. We cannot use / as path separator in url because Turbine has a special url encoding method named path_info which is name/value format. We have to change / to , in url path parameters. Code of 'F'ile'S'*_erv.java is:

package com.zamek.portal_zamek.modules.screens;

import java.io.ByteArrayOutputStream;

import javax.activation.MimetypesFileTypeMap;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.turbine.modules.screens.RawScreen;
import org.apache.turbine.util.RunData;

import com.zamek.portal_zamek.services.directory.DirectoryService;
import com.zamek.portal_zamek.services.directory.TurbineDirectory;

public class FileServ extends RawScreen {
    private final static String LOGHEADER = "com.zamek.portal_zamek.modules.screens.FileServ.";
    protected Log log = LogFactory.getLog(this.getClass());
    
    @Override
    protected void doOutput(RunData data) throws Exception {
        if (log.isDebugEnabled())
            log.debug(LOGHEADER + "doOutput, enter");
        try {
            String repo = data.getParameters().get(DirectoryService.CTX_REPO),
                   id = data.getParameters().get(DirectoryService.CTX_ID),
                   fileName = data.getParameters().get(DirectoryService.CTX_FILE);
            if (repo == null || id == null || fileName == null)
                return;
    
    
            String fName = fileName.replace(',', '/'); // restore / in url

            // private files only for hasloggedin users
            if (fName.startsWith(DirectoryService.MASK_PRIVATE) &&
                 ! data.getUser().hasLoggedIn() )
                return;
    
            ByteArrayOutputStream bos = TurbineDirectory.getFile(repo, id, fName);
            if (bos == null)
                return;
            bos.writeTo(data.getResponse().getOutputStream());
        }
        catch (Exception e) {
            log.error(LOGHEADER + "doOutput error:" + e.getMessage());
        }
    }

    @Override
    protected String getContentType(RunData data) {
        String fileName = data.getParameters().get(DirectoryService.CTX_FILE);
        if (StringUtils.isEmpty(fileName))
            return null;
        String ext = fileName.substring(fileName.lastIndexOf('.'));
        return new MimetypesFileTypeMap().getContentType(ext);
    }

}

setting FCKEditor in Velocity forms

The best way is to make two macros for setting FCKEditor:

#macro (declareFCK)
    $page.addScript($content.getURI("scripts/FCKeditor/fckeditor.js"))
#end

which is initialize editor, and

#macro (textAreaFckField $name $value $height $toolbarSet $action $repo $id)
    ##pre("name:$name value:$value height:$height toolbarset:$toolbarSet action:$action, repo:$repo, id:$id")
    <textarea id="$name" class="frminput" name="$name">$!value</textarea>
    <script type="text/javascript">
        #if ($height)
              var oFCKeditor = new FCKeditor( '$name','100%','$height', 'Default', '') ;
        #else
              var oFCKeditor = new FCKeditor( '$name' ) ;
        #end
          oFCKeditor.BasePath = "$content.getURI('scripts/FCKeditor/')";
        #if ($toolbarSet)
            oFCKeditor.ToolbarSet = "$toolbarSet";
        #else
            oFCKeditor.ToolbarSet = "Default" ;
        #end
        #if ($action.length() != 0)
            #set($connector=$link.setAction($action).addPathInfo("repo", $repo).addPathInfo("id",$id).toString())
            oFCKeditor.Config.LinkBrowserURL = oFCKeditor.BasePath + "editor/filemanager/browser/default/browser.html?Connector=$connector";
            oFCKeditor.Config.ImageBrowserURL = oFCKeditor.BasePath + "editor/filemanager/browser/default/browser.html?Connector=$connector/Type/Image";
            oFCKeditor.Config.FlashBrowserURL = oFCKeditor.BasePath + "editor/filemanager/browser/default/browser.html?Connector=$connector/Type/Flash";
        #else
            oFCKeditor.Config.LinkBrowserURL = null;
            oFCKeditor.Config.ImageBrowserURL = null;
            oFCKeditor.Config.FlashBrowserURL = null;
        #end
        oFCKeditor.Config.AutoDetectLanguage = false ;
        oFCKeditor.Config.DefaultLanguage = "hu" ;
        oFCKeditor.ReplaceTextarea ();
    </script>
#end

which is a form field for FCKEditor parameters are:

  • name: name of field
  • value: value of field
  • height: height of_* 'FC'KE'ditor in pixels
  • toolbarset: there are different toolbarset, "" will be "Default" set
  • action: name of action to handle 'FC'KE'ditor requests, default is 'F'ck'A'*_ction
  • repo: name of sub repository, for example user. It is mapping under resources/ directory ( services.DirectoryService.repository in TR.props)
  • id: id of identity in repo. for example user's id. It will mapping under resources/user/42 if user's id is 42

Let's see a form example:

#headerTitle("User's data")
$data.setLayoutTemplate("Default.vm")
#declareFCK()
<table width="100%" border="0">
...
<form name="userreg" method="post" action='$link.setAction("UserReg").toString()'>
...
        #textAreaFckField("intro" $showUser.Intro 500 "Default" "UserFile" "user" $showUser.UserId)
...
</form>
...
</table>

So, it works, but you need some utilities to complete First is_* 'F'ile'I'*_nfo class:

package com.zamek.portal_zamek.util;

import org.w3c.dom.Element;

/**
 * simple class to serve file informations for TurbineirectoryService
 * @author zamek
 *
 */
public class FileInfo {

	private String name;
	private long size;
	
	public final static String XML_NAME = "name";
	public final static String XML_SIZE = "size";
	
	public FileInfo (String name, long size) {
		this.name = name;
		this.size = size;
	}
	
	public String getName () {
		return name;
	}
	
	public long getSize () {
		return size;
	}
	
	public Element setElement (Element element, long divider) {
		if (element == null)
			return null;
		element.setAttribute(XML_NAME, name);
		element.setAttribute(XML_SIZE, 
				Long.toString(
						divider > 1 ? size / divider : size));
		return element;
	}
}

and the last (I promice!) class is_* 'F'ile'U_'_'til:

package com.zamek.portal_zamek.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Vector;
import org.apache.commons.fileupload.FileItem;

import com.zamek.portal_zamek.PortalException;
import com.zamek.portal_zamek.ZamekConst;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * @author zamek Created 2004.05.07.16:18:41
 * @version $Id: FileUtils.java,v 1.2 2006/02/07 15:25:12 zamek Exp $
 */
public class FileUtils implements ZamekConst {
	private final static String LOGHEADER = "com.zamek.portal_zamek.util.FileUtils.";

	protected static Log log = LogFactory.getLog(FileUtils.class);

	public final static int CMD_OK_EXITVALUE = 0;

	public final static String BIN_DIRECTORY = "/bin/";

	protected final static int MAX_BUFFER_SIZE = 1024;

	/**
	 * Execute a system shell command
	 * 
	 * @param path
	 *            path of command
	 * @param command
	 *            command
	 * @param params
	 *            parameters of command
	 * @throws PortalException
	 * @throws IOException
	 * @throws InterruptedException
	 */
	public static int executeShell(final String path, final String command, final String[] params)
			throws PortalException, IOException, InterruptedException {
        if (command == null || command.length() == 0) 
            throw new PortalException (MSG_EMPTY_COMMAND);
		StringBuffer cmd = new StringBuffer(pathEnd(path));
		cmd.append(command);
		if (params != null && params.length > 0)
			for (int i = 0; i < params.length; i++)
				cmd.append(" ").append(params[i]);
		Process process = Runtime.getRuntime().exec(cmd.toString());
		process.waitFor();
		if (process.exitValue() != CMD_OK_EXITVALUE)
			throw new PortalException(MSG_ERROR_EXIT + process.exitValue());
		return process.exitValue();
	}

	/**
	 * Make a directory from path. Path must be exists!
	 * 
	 * @param path
	 *            parent path
	 * @param newDir
	 *            name of new directory
	 * @throws PortalException
	 */
	public static String makeDir(String path, final String newDir)
			throws PortalException {
		File file = new File(path);
		
        if (! file.isDirectory() || ! file.exists() ) 
            throw new PortalException(MSG_PARENT_NOTEXISTS + path);
    
		path = pathEnd(path);
        
		String dirName = path + newDir;
		file = new File(dirName);
		if (file.exists())
			return dirName + "/";
		file.mkdir();
		return dirName + "/";
	}
	
	/**
	 * Make a named directory 
	 * @param newDir path and name of new directory
	 */
	public static void makeDir (final String newDir) {
		File file = new File (newDir);
		if (file.exists())
			return;
		file.mkdir();
	}
	
	/**
	 * getting size of file or directory
	 * @param file File object representing a file or Directory
	 * @return size of file or Directory
	 */
	public static long getSize (File file) {
		if (file == null)
			return 0;
		if (file.isFile())
			return file.length();
		File[] files = file.listFiles();
		if (files == null)
			return 0;
		long size = 0;
		for (int i=0; i<files.length; i++) 
			size += getSize (files[i]);
		return size;
	}
	
	/**
	 * getting size of file or Directory
	 * @param name String name of file or directory
	 * @return size of file or directory
	 */
	public static long getSize(final String name) {
		File file = new File(name);
		return getSize (file);
	}
	
	
	/**
     * Remove directory
	 * 
	 * @param dir
	 *             name of deleting directory
	 * @param force
	 *            force delete if not empty
	 * @throws PortalException
	 * @throws IOException
	 */
	public static void removeDir(final String dir, boolean force)
			throws PortalException, IOException {
		File file = new File(dir);
        if (!file.isDirectory())
            throw new PortalException (MSG_DIR_NOT_EXISTS);
		File[] files = file.listFiles();
        if (!force && files.length > 0) 
            throw new PortalException(MSG_DIR_NOT_EMPTY);
		if (force && files.length > 0) {
			for (int i = 0; i < files.length; i++)
				files[i].delete();
		}
		file.delete();
	}

	/**
	 * delete directory if it is empty
	 * 
	 * @param dir
	 *            name of deleteing directory
	 * @throws PortalException
	 * @throws IOException
	 */
	public void removeDir(final String dir) throws PortalException, IOException {
		removeDir(dir, false);
	}

	/**
	 * append pathSeparator after path if it is not exists
	 * 
	 * @param path
	 * @return
	 */
	public static String pathEnd(final String path) {
		return path.charAt(path.length() - 1) == File.separatorChar ? path
				: path + File.separatorChar;
	}

	/**
	 * return less long value
	 * 
	 * @param a
	 * @param b
	 * @return
	 */
	public static long min(long a, long b) {
		return Math.min(a, b);
	}

	/**
	 * return greates long value
	 * 
	 * @param a
	 * @param b
	 * @return
	 */
	public static long max(long a, long b) {
		return Math.max(a, b);
	}

	/**
	 * return less int value
	 * 
	 * @param a
	 * @param b
	 * @return
	 */
	public static int min(int a, int b) {
		return Math.min(a, b);
	}

	/**
	 * return greatest int value
	 * 
	 * @param a
	 * @param b
	 * @return
	 */
	public static int max(int a, int b) {
		return Math.max(a, b);
	}

	/**
	 * delete file
	 * 
	 * @param name
	 * @throws PortalException
	 * @throws IOException
	 */
	public static void deleteFile(final String name) throws PortalException,
			IOException {
		File file = new File(name);
        if (!file.exists())
            throw new PortalException(MSG_FILE_NOT_EXISTS + name);
		file.delete();
	}

    /**
     * secure file delete. It must be setting separate path and filename.
     * remove beginning separatorChar from name. (it cannot be delete from /) 
     * @param path
     * @param file
     * @throws PortalException
     * @throws IOException
     */
    public static void deleteFile(String path, String file) throws PortalException, IOException {
        path = pathEnd(path);
        while (file.charAt(0) == File.separatorChar)
            file = file.substring(1);
        File f = new File (new StringBuffer(path).append(file).toString());
        if (!f.exists())
            throw new PortalException(new StringBuffer (MSG_FILE_NOT_EXISTS).append(f.getName()).toString());
        f.delete();
    }
    
	/**
	 * copy file. 
     * file másolása. Ha targetCheck true és létezik a cél file,
	 * PortalException-t generál
	 * 
	 * @param source
	 *            source (include path)
	 * @param dest
	 *            target (include path)
	 * @param targetCheck
	 *            checking target before copy. 
     *            Throws an PortalException if it is true and target is exists
	 * @throws PortalException
	 * @throws IOException
	 */
	public static void copyFile(final String source, final String dest, boolean targetCheck)
			throws PortalException, IOException {
		if (log.isDebugEnabled())
			log.debug(LOGHEADER + "copyFile source:"+source+", dest:"+dest);

        File src = new File(source);
        if (!src.exists()) 
            throw new PortalException(MSG_FILE_NOT_EXISTS + source);

        File dst = new File(dest);
		if (targetCheck && dst.exists())
            throw new PortalException(MSG_TARGET_EXISTS + dest);

        FileInputStream fis = new FileInputStream(src);
		FileOutputStream fos = new FileOutputStream(dst);
		byte[] buffer = new byte[Math.min((int) src.length(), MAX_BUFFER_SIZE)];
		while (fis.read(buffer) != -1)
			fos.write(buffer);
		fis.close();
		fos.close();
	}

	/**
	 * copy file. It doesn't check exists of target 
	 * 
	 * @param source
	 *            source (include path)
	 * @param dest
	 *            target (include path)
	 * @throws PortalException
	 * @throws IOException
	 */
	public static void copyFile(final String source, final String dest)
			throws PortalException, IOException {
		copyFile(source, dest, false);
	}

	/**
	 * move file. 
     * file mozgatása. Ha targetCheck true és létezik a cél file,
	 * PortalException-t generál
	 * 
	 * @param source
	 *            source (include path)
	 * @param dest
	 *            target (include path)
	 * @param targetCheck
     *            checking target before copy. 
     *            Throws an PortalException if it is true and target is exists
	 * @throws PortalException
	 * @throws IOException
	 */
	public static void moveFile(final String source, final String dest, boolean targetCheck)
			throws PortalException, IOException {
		copyFile(source, dest, targetCheck);
		deleteFile(source);
	}

	/**
	 * move file. it doesn't check exists of target 
	 * 
	 * @param source
	 *            source (include path)
	 * @param dest
	 *            target (include path)
	 * @throws PortalException
	 * @throws IOException
	 */
	public static void moveFile(String source, String dest)
			throws PortalException, IOException {
		moveFile(source, dest, false);
	}

	/**
	 * copy uploaded file to toDir with newName.
	 * @param fileItem
	 *            upload fileItem
	 * @param toDir
	 *            target directory
	 * @param newName
	 *            name of new fileName
	 * @throws PortalException
	 *             if fileItem == null || fielItem.size() == 0
	 * @throws Exception
	 */
	public static String uploadFile(FileItem fileItem, final String toDir,
			final String newName) throws PortalException, Exception {
		if (log.isDebugEnabled())
				log.debug(LOGHEADER + "uploadFile enter, todir:"+toDir+
                          ",newname:"+newName);
        if (fileItem == null || fileItem.getSize() == 0)
            throw new PortalException(MSG_FILE_NULL_OR_EMPTY);
        StringBuffer sb = new StringBuffer(pathEnd(toDir)).append(newName);
		fileItem.write(new File(sb.toString()));
		return sb.toString();
	}

	/**
	 * separate filename into path, name, ext
	 * 
	 * @param source
	 *            input filaname
	 * @return [0] : path, [1]: name, [2]: ext
	 */
	public static String[] stripFileName(final String source) {
		String[] res = new String[3];
        
		int extPos = source.lastIndexOf('.');
		res[2] = extPos > 0 ? source.substring(extPos) : EMPTY_STR;
        
		String src = source.substring(0, extPos);
		
        int namePos = source.lastIndexOf(File.separatorChar);
		if (namePos >= 0) {
			res[1] = src.substring(++namePos);
			res[0] = src.substring(0, namePos);
		} 
        else {
			res[0] = EMPTY_STR;
			res[1] = EMPTY_STR;
		}
		return res;
	}

    /**
     * get extent of filename
     * @param name
     * @return
     */
	public static String getFileExt(final String name) {
		int extPos = name.lastIndexOf('.');
		return extPos > 0 ? name.substring(extPos) : EMPTY_STR;
	}

	/**
	 * extract name from absolutePath+fileName
	 * @param fileName
	 * @return
	 */
	public static String getFileName(final String fileName) {
		if (StringUtils.isEmpty(fileName))
			return fileName;
		int namePos = fileName.lastIndexOf(File.pathSeparator);
		return namePos == -1 
			? fileName 
			: fileName.substring(namePos); 
	}
	/**
	 * list content of directory. It can filter with optional parameters
	 * @param dir
	 *            listing directory. It check exists, readable, directory check
	 * @param mask
     *            Optional filter mask. It masks with String.matches()
	 * @return list of files
	 */
	public static List<String> getFilesInDirectory(final String dir, final String mask) {
		String[] files;
		Vector<String> result = new Vector<String>();
		File directory = new File(dir);
		if (directory.exists() && directory.canRead()
				&& directory.isDirectory()) {
			files = directory.list();
			if (mask != null && mask.length() > 0) {
				for (int i = 0; i < files.length; i++) {
					String fName = files[i];
					if (fName.matches(mask))
						result.add(fName);
				}
			} 
            else {
				for (int i = 0; i < files.length; i++)
					result.add(files[i]);
			}
			return result;
		}
		return null;
	}

	public static List<String> getDirectoryNames (final String dir, 
					final String includeMask, final String excludeMask) {	
		String[] files;
		Vector<String> result = new Vector<String>();
		File directory = new File(dir);
		if (directory.exists() && directory.canRead()
				&& directory.isDirectory()) {
			files = directory.list();
			for (int i=0; i < files.length; i++) {
				String cName = files[i];
				File subDir = new File (dir + File.separatorChar + cName);
				if (! subDir.isDirectory())
					continue;
				if (includeMask == null && excludeMask == null) {
					result.add(cName);
					continue;
				}
				if (excludeMask != null && cName.matches(excludeMask)) 
					continue;
				if (includeMask != null && cName.matches(includeMask))
					result.add(cName);					
			}			
			return result;
		}
		return null;
	}
	
	public static List<FileInfo> getFileNames (final String dir, 
					final String includeMask, final String excludeMask) {
		String[] files;
		Vector<FileInfo> result = new Vector<FileInfo>();
		File directory = new File(dir);
		if (directory.exists() && directory.canRead()
				&& directory.isDirectory()) {
			files = directory.list();
			for (int i=0; i < files.length; i++) {
				String cName = files[i];
				File file = new File (dir + File.separatorChar + cName);
				if (! file.isFile())
					continue;
				if (includeMask == null && excludeMask == null) {
					result.add(new FileInfo(cName, file.length()));
					continue;
				}
				if (excludeMask != null && cName.matches(excludeMask)) 
					continue;
				if ( (includeMask == null) || (includeMask != null && cName.matches(includeMask)) ) {
					result.add(new FileInfo(cName, file.length()));					
				}
			}			
			return result;
		}
		return null;
		
	}
	
	/**
	 * list content of directory. It can filter with optional parameters
	 * @param dir
	 *            listing directory. It check exists, readable, directory check
	 * @param includeMask
     *            Optional filter mask. It masks with String.matches()
	 * @param excludeMask
     *            Optional filter mask. It masks with String.matches()
	 * @return list of files
	 */
	public static List getFilesInDirectory(final String dir, final String includeMask, final String excludeMask) {
		String[] files;
		Vector<String> result = new Vector<String>();
		File directory = new File(dir);
		boolean chkInclude = includeMask != null && includeMask.length() > 0,
				chkExclude = excludeMask != null && excludeMask.length() > 0;
		
		if (directory.exists() && directory.canRead()
				&& directory.isDirectory()) {
			files = directory.list();
			for (int i = 0; i < files.length; i++) {
				String fName = files[i];
				if ( (chkExclude && (! fName.matches(excludeMask)) &&
					  chkInclude && fName.matches(includeMask)))
						result.add(fName);
				else 
					if (chkInclude && fName.matches(includeMask))
						result.add(fName);
					else 
						result.add(fName);
			}
			return result;
		}
		return null;
	}
	
	
    /**
     * checks end of name with needExtent
     * @param name 
     * @param needExtent
     * @return
     */
    public static boolean checkExtent (final String name, final String needExtent) {
       return needExtent != null && name != null &&
              name.length() >= needExtent.length() &&
              name.endsWith(needExtent);
    }
        
	final static char BACK_SLASH = '\\',
					  SLASH = '/',
					  COLON = ':',
					  SPACE = ' ',
					  UNDER_LINE = '_';
	final static char [] INTERNATIONALS = {'á','í','ű','ő','ü','ö','ú','ó','é',
						  				   'Á','Í','Ű','Ő','Ü','Ö','Ú','Ó','É',
						  				   '?','?','?','?'};
	final static char[] ASCII = {'a','i','u','o','u','o','u','o','e',
								 'A','I','U','O','U','O','U','O','E',
								 'u','o','U','O'};

	/**
	 * Normalize file name.
	 * replace ':' to '_', '\' to '/', 
	 * international chars to ascii representant or '_'  
	 * 
	 * @param s
	 * @return normalized filename
	 */
	public static String normalizeFileName(String s) {
		if (StringUtils.isEmpty(s))
			return s;
		if (s.indexOf(BACK_SLASH) > 0 && s.indexOf(COLON) > 0) 
			s = processWinFileName (s);
		s = s.replace(SPACE, UNDER_LINE);
		return changeInternationalChars(s);
	}

	private static String processWinFileName(String s) {
		s= s.replace(COLON, UNDER_LINE);
		return s.replace(BACK_SLASH, SLASH);
	}
	
	private static String changeInternationalChars(String s) {
		for (short i=0; i<s.length(); i++) {
			char at = s.charAt(i); 
			if (at > 127) {
				for (short j=0; j<INTERNATIONALS.length; j++) {
					if (at == INTERNATIONALS[j]) {
						s = s.replace(at, ASCII [j]);
						break;
					}
				}
				s = s.replace(at, UNDER_LINE);
			}
		}
		return s;
	}
    
}
  • No labels