Hi

I am currently implementing quota for Slide.

In the mailling list, I read that slide developpers were interrested in 
including this feature into slide's kernel.

So I'm searching feedback before going further in my implementation to 
be sure it feets slide's developpers needs and way of coding.

I began the implementation following advices Dirk made on an email dated 
from august 2001 in which he was saying :
- the more generic way to integrate quota is to do it into
   ContentInterceptor class
- user's quota and bytes used can be stored into protected properties
   from user's node
- the algorithm could be resumed as this : in preStoreContent method
   check if user has enougth space and put a lock on the user, in the
   postStoreContent method update bytes used and unlock user's node.
- to deal with content removing, add a postRemoveContent method to the
   ContentInterceptor class.
- what to do if clients don't send a contentLength ?
- to allow webdav methods to indicate webdav clients the reason why the
   operation failed, ContentInterceptor class must throw a special
   exception that has to be catched by webdav methods to send webdav
   clients an insufficient storage error code (507)

So here is the begin of an implementation that takes in account (almost) 
all these advices.

I have added to package content a new Exception called 
InsufficientStorageException

I think that the best place to add a parameter to enable quota is in the 
<configuration> section of the domain.xml file.
This one is read by the NamespaceConfig class that instanciates the 
ContentInterceptors.
I've added to the ContentInterceptor class a new contructor that has to 
be called by NamespaceConfig class if Domain.xml file tells to enable quota.

Some questions I am asking myself :

Why is there an array of contentInterceptor instances into 
NamespaceConfig class rather than a unique instance ?

Some ContentInterceptor methods need a contentHelper and/or a lockHelper 
to do their job. What would be the best : to give them to constructor to
initialize private properties or to give them as method argument ?

Is postStoreContent method called if an exception has been raised by 
preContentStore method or someone else ?

Which subject, type and expiration use to lock the user's node ? I think 
that with this token we can only use the user as subject but if the lock 
is put by the user, it can be kill by the user, isn't it ?

With a lock on the user's node, user won't be able to perform any other 
operation ?

What could be done with webdav clients that don't send a contentLength 
header ?

What could be done better than it is in my code ?

All remarks are welcome

JP

/*
 * $Header: 
/home/cvs/jakarta-slide/src/share/org/apache/slide/content/ContentInterceptor.java,v 
1.3 2001/05/14 09:19:32 remm Exp $
 * $Revision: 1.3 $
 * $Date: 2001/05/14 09:19:32 $
 *
 * ====================================================================
 *
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 1999 The Apache Software Foundation.  All rights 
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:  
 *       "This product includes software developed by the 
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written 
 *    permission, please contact [EMAIL PROTECTED]
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 * [Additional notices, if required by prior licensing conditions]
 *
 */ 

package org.apache.slide.content;

import java.util.Date;
import java.util.Enumeration;
import org.apache.slide.lock.*;
import org.apache.slide.store.Store;
import org.apache.slide.common.SlideToken;
import org.apache.slide.common.ServiceAccessException;
import org.apache.slide.security.AccessDeniedException;
import org.apache.slide.structure.ObjectNotFoundException;
import org.apache.slide.structure.LinkedObjectNotFoundException;

/**
 * Slide content interceptor class.
 * 
 * @author <a href="mailto:[EMAIL PROTECTED]";>Remy Maucherat</a>
 * @author <a href="mailto:[EMAIL PROTECTED]";>Jean-Philippe Courson</a>
 */
public class ContentInterceptor {

    
    // ----------------------------------------------------------- Constructors


    /**
     * Default Constructor with quota disabled.
     */
    public ContentInterceptor() {
        this.quotaEnabled = false;
    }


    /**
     * Constructor to use to enable quota.
     *
     * @param usersPath Users nodes path.
     */
    public ContentInterceptor(String usersPath) {
        this.quotaEnabled = true;
        this.usersPath = usersPath;
    }


    // ----------------------------------------------------- Instance Variables
    
    
    /**
     * Quota enabled ?
     */
    private boolean quotaEnabled;

    /**
     * Users nodes path.
     */
    private String usersPath;

    
    // ------------------------------------------------------ Protected methods
    
    
    /**
     * That method will be called just before storing content.
     *
     * @param token Slide's token
     * @param revisionDescriptors New node revision descriptors
     * @param revisionDescriptor New node revision descriptor
     * @param revisionContent New node revision content
     * @param contentHelper Content helper
     * @param lockHelper Lock helper
     */
    protected void preStoreContent(SlideToken token, 
                                   NodeRevisionDescriptors revisionDescriptors,
                                   NodeRevisionDescriptor revisionDescriptor,
                                   NodeRevisionContent revisionContent,
                                   Content contentHelper,
                                   Lock lockHelper)
        throws InsufficientStorageException, AccessDeniedException,
               ServiceAccessException, ObjectNotFoundException,
               LinkedObjectNotFoundException, ObjectLockedException {

        // quota enabled ?
        if(!quotaEnabled) return;

        // retrieve new content length
        long contentLength = revisionDescriptor.getContentLength();
        if(contentLength <= 0) return;

        // retrieve user's bytesUsed and quota properties


        // build user's node uri
        String userUri = getUserUri(token);

        // retrieve user node revision descriptor
        NodeRevisionDescriptors userRevisionDescriptors = 
contentHelper.retrieve(token, userUri.toString());
        NodeRevisionDescriptor userRevisionDescriptor = null;
        try {
            userRevisionDescriptor = contentHelper.retrieve(token, 
userRevisionDescriptors);
        }
        catch(RevisionDescriptorNotFoundException e) {
            // should never happen
        }

        // retrieve user node bytesUsed property
        NodeProperty property = userRevisionDescriptor.getProperty("bytesUsed");
        if(property == null) throw new ServiceAccessException((Store)contentHelper, 
"Quota enabled but user "+userUri+" has no bytesUsed property");
        long bytesUsed = Long.parseLong((String)property.getValue());

        // retrieve user node quota property
        property = userRevisionDescriptor.getProperty("quota");
        if(property == null) throw new ServiceAccessException((Store)contentHelper, 
"Quota enabled but user "+userUri+" has no quota property");
        long quota = Long.parseLong((String)property.getValue());


        // if user quota exhausted throws InsufficientStorageException
        if(bytesUsed+contentLength > quota) throw new 
InsufficientStorageException("Quota exhausted for user "+userUri);


        // lock user node
        // FIXME: WHICH TYPE OF LOCK (EXCLUSIVE ?, SUBJECT ?, LENGTH ?)
        Date expiration = new Date(System.currentTimeMillis()+3600000);
        NodeLock lock = new NodeLock(userUri, userUri, "/actions/read", expiration, 
false, true);
        try {
            lockHelper.lock(token, lock);
        }
        catch(ObjectIsAlreadyLockedException e) {
            // FIXME: WHAT SHOULD BE DONE HERE ?
        }
    }
    
    
    /**
     * That method will be called just after storing content.
     *
     * @param token Slide's token
     * @param revisionDescriptors New node revision descriptors
     * @param revisionDescriptor New node revision descriptor
     * @param revisionContent New node revision content
     * @param contentHelper Content helper
     * @param lockHelper Lock helper
     */
    protected void postStoreContent(SlideToken token, 
                                    NodeRevisionDescriptors revisionDescriptors,
                                    NodeRevisionDescriptor revisionDescriptor,
                                    NodeRevisionContent revisionContent,
                                    Content contentHelper,
                                    Lock lockHelper)
        throws AccessDeniedException, ServiceAccessException,
               ObjectNotFoundException, LinkedObjectNotFoundException,
               ObjectLockedException {

        // quota enabled ?
        if(!quotaEnabled) return;

        // update user's node bytesUsed properties
        updateBytesUsed(token, true, revisionDescriptor, contentHelper);

        // build user's node uri
        String userUri = getUserUri(token);

        // unlock user's node
        try {
            Enumeration locks = lockHelper.enumerateLocks(token, userUri, false);
            while(locks.hasMoreElements()) {
                NodeLock lock = (NodeLock)locks.nextElement();
                if(lock.getSubjectUri().equals(userUri) && 
lock.getTypeUri().equals("/actions/read") && lock.isExclusive()) 
lockHelper.unlock(token, lock);
            }
        }
        catch(LockTokenNotFoundException e) {
            // to much time ellapsed, nothing we can do
        }
    }
    
    
    /**
     * That method will be called just after removing content.
     *
     * @param token Slide's token
     * @param revisionDescriptors New node revision descriptors
     * @param revisionDescriptor New node revision descriptor
     * @param revisionContent New node revision content
     * @param contentHelper Content helper
     */
    protected void postRemoveContent(SlideToken token, 
                                     NodeRevisionDescriptors revisionDescriptors,
                                     NodeRevisionDescriptor revisionDescriptor,
                                     NodeRevisionContent revisionContent,
                                     Content contentHelper)
        throws AccessDeniedException, ServiceAccessException,
               ObjectNotFoundException, LinkedObjectNotFoundException,
               ObjectLockedException {

        // quota enabled ?
        if(!quotaEnabled) return;

        // update user's node bytesUsed properties
        updateBytesUsed(token, false, revisionDescriptor, contentHelper);
    }


    /**
     * That method will be called just after retrieving content.
     * 
     * @param token Slide's token
     * @param revisionContent null when the descriptor is retrieved
     * @param revisionDescriptors null when the content is retrieved
     * @param revisionContent Node revision content
     */
    protected void postRetrieveContent(SlideToken token,
                                       NodeRevisionDescriptors revisionDescriptors,
                                       NodeRevisionDescriptor revisionDescriptor,
                                       NodeRevisionContent revisionContent) {
    }
    

    // ------------------------------------------------------ Private methods


    /**
     * Method used to update user's node bytesUsed property.
     *
     * @param token Slide's token
     * @param addValue If set to true, new node contentLength is added else it is 
substracted
     * @param revisionDescriptor New node revision descriptor
     * @param contentHelper Content helper
     */
    private void updateBytesUsed(SlideToken token,
                                 boolean addValue,
                                 NodeRevisionDescriptor revisionDescriptor,
                                 Content contentHelper)
        throws AccessDeniedException, ServiceAccessException,
               ObjectNotFoundException, LinkedObjectNotFoundException,
               ObjectLockedException {

        // retrieve new content length
        long contentLength = revisionDescriptor.getContentLength();
        if(contentLength <= 0) return;

        // retrieve user's bytesUsed properties


        // build user's node uri
        String userUri = getUserUri(token);

        // retrieve user's node revision descriptor
        NodeRevisionDescriptors userRevisionDescriptors = userRevisionDescriptors = 
contentHelper.retrieve(token, userUri.toString());
        NodeRevisionDescriptor userRevisionDescriptor = null;
        try {
            userRevisionDescriptor = contentHelper.retrieve(token, 
userRevisionDescriptors);
        }
        catch(RevisionDescriptorNotFoundException e) {
            // should never happen
        }

        // retrieve user's node bytesUsed property
        NodeProperty property = userRevisionDescriptor.getProperty("bytesUsed");
        if(property == null) throw new ServiceAccessException((Store)contentHelper, 
"Quota enabled but user "+userUri+" has no bytesUsed property");
        long bytesUsed = Long.parseLong((String)property.getValue());


        // computes new bytesUsed value
        if(addValue) bytesUsed = bytesUsed+contentLength;
        else {
            bytesUsed = bytesUsed-contentLength;
            if(bytesUsed < 0) bytesUsed = 0;
        }

        // update user's node totalBytes properties
        userRevisionDescriptor.setProperty("bytesUsed", Long.toString(bytesUsed), 
true);
        try {
            contentHelper.store(token, userUri, userRevisionDescriptor, null);
        }
        catch(RevisionDescriptorNotFoundException e) {
            // should never happen
        }
        catch(RevisionNotFoundException e) {
            // should never happen
        }
    }


    /**
     * Method used to build user's node uri.
     *
     * @param token Slide's token
     *
     * @return user's node uri
     */
    private String getUserUri(SlideToken token) {
        StringBuffer sb = new StringBuffer(usersPath);
        sb.append("/");
        sb.append(token.getCredentialsToken().getPublicCredentials());
        return sb.toString();
    }
}



--
To unsubscribe, e-mail:   <mailto:[EMAIL PROTECTED]>
For additional commands, e-mail: <mailto:[EMAIL PROTECTED]>

Reply via email to