org.olat.core.util.servlets.VFSDirContext.java Source code

Java tutorial

Introduction

Here is the source code for org.olat.core.util.servlets.VFSDirContext.java

Source

/**
 * OLAT - Online Learning and Training<br>
 * http://www.olat.org
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License"); <br>
 * you may not use this file except in compliance with the License.<br>
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing,<br>
 * software distributed under the License is distributed on an "AS IS" BASIS, <br>
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
 * See the License for the specific language governing permissions and <br>
 * limitations under the License.
 * <p>
 * Copyright (c) 1999-2006 at Multimedia- & E-Learning Services (MELS),<br>
 * University of Zurich, Switzerland.
 * <p>
 */

package org.olat.core.util.servlets;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;

import javax.naming.Binding;
import javax.naming.Context;
import javax.naming.NameAlreadyBoundException;
import javax.naming.NameClassPair;
import javax.naming.NameNotFoundException;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.NotContextException;
import javax.naming.OperationNotSupportedException;
import javax.naming.directory.AttributeModificationException;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InvalidAttributesException;
import javax.naming.directory.InvalidSearchControlsException;
import javax.naming.directory.InvalidSearchFilterException;
import javax.naming.directory.ModificationItem;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;

import org.apache.naming.resources.BaseDirContext;
import org.apache.naming.resources.Resource;
import org.apache.naming.resources.ResourceAttributes;
import org.olat.core.commons.modules.bc.meta.MetaInfo;
import org.olat.core.commons.modules.bc.meta.MetaInfoHelper;
import org.olat.core.commons.modules.bc.meta.tagged.MetaTagged;
import org.olat.core.id.Identity;
import org.olat.core.util.FileUtils;
import org.olat.core.util.UserSession;
import org.olat.core.util.notifications.NotificationsManager;
import org.olat.core.util.notifications.SubscriptionContext;
import org.olat.core.util.vfs.Quota;
import org.olat.core.util.vfs.VFSConstants;
import org.olat.core.util.vfs.VFSContainer;
import org.olat.core.util.vfs.VFSItem;
import org.olat.core.util.vfs.VFSLeaf;
import org.olat.core.util.vfs.VFSManager;
import org.olat.core.util.vfs.VFSStatus;
import org.olat.core.util.vfs.callbacks.VFSSecurityCallback;
import org.olat.core.util.vfs.version.Versionable;
import org.olat.core.util.vfs.version.VersionsManager;

/**
 * Filesystem Directory Context implementation helper class.
 * 
 * @author Remy Maucherat
 */

public class VFSDirContext extends BaseDirContext {

    // private static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(VFSDirContext.class);

    // -------------------------------------------------------------- Constants

    /**
     * The string manager for this package.
     */
    private StringManager smgr = new StringManager();

    /**
     * The descriptive information string for this implementation.
     */
    public static int bufferSize = 2048;

    private Identity identity;
    private UserSession userSession;

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

    /**
     * Builds a file directory context using the given environment.
     */
    public VFSDirContext() {
        super();
    }

    /**
     * Builds a file directory context using the given environment.
     */
    public VFSDirContext(Hashtable env) {
        super(env);
    }

    // ----------------------------------------------------- Instance Variables

    /**
     * The document base directory.
     */
    protected VFSItem base = null;

    /**
     * Absolute normalized filename of the base.
     */
    protected String absoluteBase = null;

    /**
     * Case sensitivity.
     */
    protected boolean caseSensitive = true;

    /**
     * Allow linking.
     */
    protected boolean allowLinking = false;

    // ------------------------------------------------------------- Properties

    /**
     * Set the document root.
     * 
     * @param vfsItem The new document root
     * @exception IllegalArgumentException if the specified value is not supported by this implementation
     */
    public void setVirtualDocBase(VFSItem vfsItem) {
        base = vfsItem;
    }

    public Identity getIdentity() {
        return identity;
    }

    public void setIdentity(Identity identity) {
        this.identity = identity;
    }

    public UserSession getUserSession() {
        return userSession;
    }

    public void setUserSession(UserSession userSession) {
        this.userSession = userSession;
    }

    public VFSItem getVirtualDocBase() {
        return base;
    }

    /**
     * Set the document root.
     * 
     * @param docBase The new document root
     * @exception IllegalArgumentException if the specified value is not supported by this implementation
     * @exception IllegalArgumentException if this would create a malformed URL
     */
    @Override
    public void setDocBase(String docBase) {

        // disabled for VFSDirContext implementation...
        throw new IllegalArgumentException("setDocBase(String) not supported by VFSDirCOntext.");
    }

    /**
     * Set case sensitivity.
     */
    public void setCaseSensitive(boolean caseSensitive) {
        this.caseSensitive = caseSensitive;
    }

    /**
     * Is case sensitive ?
     */
    public boolean isCaseSensitive() {
        return caseSensitive;
    }

    /**
     * Set allow linking.
     */
    public void setAllowLinking(boolean allowLinking) {
        this.allowLinking = allowLinking;
    }

    /**
     * Is linking allowed.
     */
    public boolean getAllowLinking() {
        return allowLinking;
    }

    public void setBuffer(int bytes) {
        bufferSize = bytes;
    }

    // --------------------------------------------------------- Public Methods

    /**
     * Release any resources allocated for this directory context.
     */
    @Override
    public void release() {

        caseSensitive = true;
        allowLinking = false;
        absoluteBase = null;
        base = null;
        super.release();

    }

    // -------------------------------------------------------- Context Methods

    /**
     * Retrieves the named object.
     * 
     * @param name the name of the object to look up
     * @return the object bound to name
     * @exception NamingException if a naming exception is encountered
     */
    @Override
    public Object lookup(String name) throws NamingException {

        VFSItem item = resolveFile(name);
        if (item == null)
            throw new NamingException(smgr.getString("resources.notFound", name));

        if (item instanceof VFSContainer) {
            VFSDirContext tempContext = new VFSDirContext(env);
            tempContext.setVirtualDocBase(item);
            return tempContext;
        } else {
            return new VFSResource(item);
        }
    }

    /**
     * Check if resource can be copied (delegation to VFS item)
     * 
     * @param name
     * @return true: can copy; false: can not copy
     */
    public boolean canCopy(String name) {
        VFSItem item = resolveFile(name);
        if (item != null && VFSConstants.YES.equals(item.canCopy()))
            return true;
        else
            return false;
    }

    /**
     * Check if resource can be written (delegation to VFS item)
     * 
     * @param name
     * @return true: can write; false: can not write
     */
    public boolean canWrite(String name) {
        // resolve item if it already exists
        VFSItem item = resolveFile(name);
        if (item == null) {
            // try to resolve parent in case the item does not yet exist
            int lastSlash = name.lastIndexOf("/");
            if (lastSlash > 0) {
                String containerName = name.substring(0, lastSlash);
                item = resolveFile(containerName);
            }
        }
        if (item == null)
            return false;

        VFSStatus status;
        if (item instanceof VFSContainer) {
            status = item.canWrite();
        } else {
            // read/write is not defined on item level, only on directory level
            status = item.getParentContainer().canWrite();
        }
        return VFSConstants.YES.equals(status);
    }

    /**
     * Check if resource can be deleted (delegation to VFS item)
     * 
     * @param name
     * @return true: can delete; false: can not delete
     */
    public boolean canDelete(String name) {
        VFSItem item = resolveFile(name);
        if (item != null && VFSConstants.YES.equals(item.canDelete())) {
            return !MetaInfoHelper.isLocked(item, userSession);
        } else
            return false;
    }

    /**
     * Check if resource can be renamed (delegation to VFS item)
     * 
     * @param name
     * @return true: can rename; false: can not rename
     */
    public boolean canRename(String name) {
        VFSItem item = resolveFile(name);
        if (item != null && VFSConstants.YES.equals(item.canRename())) {
            return !MetaInfoHelper.isLocked(item, userSession);
        } else
            return false;
    }

    /**
     * Unbinds the named object. Removes the terminal atomic name in name from the target context--that named by all but the terminal atomic part of name.
     * <p>
     * This method is idempotent. It succeeds even if the terminal atomic name is not bound in the target context, but throws NameNotFoundException if any of the
     * intermediate contexts do not exist.
     * 
     * @param name the name to bind; may not be empty
     * @exception NameNotFoundException if an intermediate context does not exist
     * @exception NamingException if a naming exception is encountered
     */
    @Override
    public void unbind(String name) throws NamingException {

        VFSItem file = resolveFile(name);
        if (file == null)
            throw new NamingException(smgr.getString("resources.notFound", name));

        VFSStatus status = file.delete();
        if (status == VFSConstants.NO)
            throw new NamingException(smgr.getString("resources.unbindFailed", name));
    }

    /**
     * Binds a new name to the object bound to an old name, and unbinds the old name. Both names are relative to this context. Any attributes associated with the old name
     * become associated with the new name. Intermediate contexts of the old name are not changed.
     * 
     * @param oldName the name of the existing binding; may not be empty
     * @param newName the name of the new binding; may not be empty
     * @exception NameAlreadyBoundException if newName is already bound
     * @exception NamingException if a naming exception is encountered
     */
    @Override
    public void rename(String oldName, String newName) throws NamingException {

        VFSItem oldFile = resolveFile(oldName);
        if (oldFile == null)
            throw new NamingException(smgr.getString("resources.notFound", oldName));

        VFSItem newFile = resolveFile(newName);
        if (newFile != null)
            throw new NameAlreadyBoundException();

        VFSStatus status = oldFile.rename(newName);
        if (status == VFSConstants.NO)
            throw new NameAlreadyBoundException();
    }

    /**
     * Enumerates the names bound in the named context, along with the class names of objects bound to them. The contents of any subcontexts are not included.
     * <p>
     * If a binding is added to or removed from this context, its effect on an enumeration previously returned is undefined.
     * 
     * @param name the name of the context to list
     * @return an enumeration of the names and class names of the bindings in this context. Each element of the enumeration is of type NameClassPair.
     * @exception NamingException if a naming exception is encountered
     */
    @Override
    public NamingEnumeration<NameClassPair> list(String name) throws NamingException {

        VFSItem file = resolveFile(name);
        if (file == null)
            throw new NamingException(smgr.getString("resources.notFound", name));
        return new NamingContextEnumeration(list(file).iterator());

    }

    /**
     * Enumerates the names bound in the named context, along with the objects bound to them. The contents of any subcontexts are not included.
     * <p>
     * If a binding is added to or removed from this context, its effect on an enumeration previously returned is undefined.
     * 
     * @param name the name of the context to list
     * @return an enumeration of the bindings in this context. Each element of the enumeration is of type Binding.
     * @exception NamingException if a naming exception is encountered
     */
    @Override
    public NamingEnumeration<Binding> listBindings(String name) throws NamingException {
        return new NamingContextEnumeration2(list(name));
    }

    /**
     * Destroys the named context and removes it from the namespace. Any attributes associated with the name are also removed. Intermediate contexts are not destroyed.
     * <p>
     * This method is idempotent. It succeeds even if the terminal atomic name is not bound in the target context, but throws NameNotFoundException if any of the
     * intermediate contexts do not exist. In a federated naming system, a context from one naming system may be bound to a name in another. One can subsequently look up
     * and perform operations on the foreign context using a composite name. However, an attempt destroy the context using this composite name will fail with
     * NotContextException, because the foreign context is not a "subcontext" of the context in which it is bound. Instead, use unbind() to remove the binding of the
     * foreign context. Destroying the foreign context requires that the destroySubcontext() be performed on a context from the foreign context's "native" naming system.
     * 
     * @param name the name of the context to be destroyed; may not be empty
     * @exception NameNotFoundException if an intermediate context does not exist
     * @exception NotContextException if the name is bound but does not name a context, or does not name a context of the appropriate type
     */
    @Override
    public void destroySubcontext(String name) throws NamingException {
        unbind(name);
    }

    /**
     * Retrieves the named object, following links except for the terminal atomic component of the name. If the object bound to name is not a link, returns the object
     * itself.
     * 
     * @param name the name of the object to look up
     * @return the object bound to name, not following the terminal link (if any).
     * @exception NamingException if a naming exception is encountered
     */
    @Override
    public Object lookupLink(String name) throws NamingException {
        // Note : Links are not supported
        return lookup(name);
    }

    /**
     * Retrieves the full name of this context within its own namespace.
     * <p>
     * Many naming services have a notion of a "full name" for objects in their respective namespaces. For example, an LDAP entry has a distinguished name, and a DNS
     * record has a fully qualified name. This method allows the client application to retrieve this name. The string returned by this method is not a JNDI composite name
     * and should not be passed directly to context methods. In naming systems for which the notion of full name does not make sense, OperationNotSupportedException is
     * thrown.
     * 
     * @return this context's name in its own namespace; never null
     * @exception OperationNotSupportedException if the naming system does not have the notion of a full name
     * @exception NamingException if a naming exception is encountered
     */
    @Override
    public String getNameInNamespace() {
        return docBase;
    }

    // ----------------------------------------------------- DirContext Methods

    /**
     * Retrieves selected attributes associated with a named object. See the class description regarding attribute models, attribute type names, and operational
     * attributes.
     * 
     * @return the requested attributes; never null
     * @param name the name of the object from which to retrieve attributes
     * @param attrIds the identifiers of the attributes to retrieve. null indicates that all attributes should be retrieved; an empty array indicates that none should be
     *            retrieved
     * @exception NamingException if a naming exception is encountered
     */
    @Override
    public Attributes getAttributes(String name, String[] attrIds) throws NamingException {

        // Building attribute list
        VFSItem file = resolveFile(name);
        if (file == null)
            throw new NamingException(smgr.getString("resources.notFound", name));
        return new VFSResourceAttributes(file);

    }

    /**
     * Modifies the attributes associated with a named object. The order of the modifications is not specified. Where possible, the modifications are performed
     * atomically.
     * 
     * @param name the name of the object whose attributes will be updated
     * @param mod_op the modification operation, one of: ADD_ATTRIBUTE, REPLACE_ATTRIBUTE, REMOVE_ATTRIBUTE
     * @param attrs the attributes to be used for the modification; may not be null
     * @exception AttributeModificationException if the modification cannot be completed successfully
     * @exception NamingException if a naming exception is encountered
     */
    @Override
    public void modifyAttributes(String name, int mod_op, Attributes attrs) {
        // not implemented
    }

    /**
     * Modifies the attributes associated with a named object using an an ordered list of modifications. The modifications are performed in the order specified. Each
     * modification specifies a modification operation code and an attribute on which to operate. Where possible, the modifications are performed atomically.
     * 
     * @param name the name of the object whose attributes will be updated
     * @param mods an ordered sequence of modifications to be performed; may not be null
     * @exception AttributeModificationException if the modification cannot be completed successfully
     * @exception NamingException if a naming exception is encountered
     */
    @Override
    public void modifyAttributes(String name, ModificationItem[] mods) {
        // not implemented
    }

    /**
     * @see javax.naming.Context#bind(java.lang.String, java.lang.Object)
     */
    @Override
    public void bind(String name, Object obj) throws NamingException {
        bind(name, obj, null);
    }

    /**
     * Binds a name to an object, along with associated attributes. If attrs is null, the resulting binding will have the attributes associated with obj if obj is a
     * DirContext, and no attributes otherwise. If attrs is non-null, the resulting binding will have attrs as its attributes; any attributes associated with obj are
     * ignored.
     * 
     * @param name the name to bind; may not be empty
     * @param obj the object to bind; possibly null
     * @param attrs the attributes to associate with the binding
     * @exception NameAlreadyBoundException if name is already bound
     * @exception InvalidAttributesException if some "mandatory" attributes of the binding are not supplied
     * @exception NamingException if a naming exception is encountered
     */
    @Override
    public void bind(String name, Object obj, Attributes attrs) throws NamingException {

        // Note: No custom attributes allowed
        VFSItem file = resolveFile(name);
        if (file != null)
            throw new NameAlreadyBoundException(smgr.getString("resources.alreadyBound", name));

        int lastSlash = name.lastIndexOf('/');
        if (lastSlash == -1)
            throw new NamingException();
        String parent = name.substring(0, lastSlash);
        VFSItem folder = resolveFile(parent);

        if (folder == null || (!(folder instanceof VFSContainer)))
            throw new NamingException(smgr.getString("resources.bindFailed", name));
        String newName = name.substring(lastSlash + 1);
        VFSLeaf childLeaf = ((VFSContainer) folder).createChildLeaf(newName);
        if (childLeaf == null)
            throw new NamingException(smgr.getString("resources.bindFailed", name));
        copyVFS(childLeaf, name, obj, attrs);

        VFSSecurityCallback callback = folder.getLocalSecurityCallback();
        if (callback != null && callback.getSubscriptionContext() != null) {
            SubscriptionContext subContext = callback.getSubscriptionContext();
            NotificationsManager.getInstance().markPublisherNews(subContext, null);
        }

        if (childLeaf instanceof MetaTagged) {
            MetaInfo infos = ((MetaTagged) childLeaf).getMetaInfo();
            if (infos != null && infos.getAuthorIdentity() == null) {
                infos.setAuthor(userSession.getIdentity().getName());
                infos.clearThumbnails();
                infos.write();
            }
        }

        // used by move operations
        if (obj instanceof VFSResource) {
            VFSResource vfsResource = (VFSResource) obj;
            if (vfsResource.vfsItem instanceof Versionable
                    && ((Versionable) vfsResource.vfsItem).getVersions().isVersioned()) {
                Versionable currentVersion = (Versionable) vfsResource.vfsItem;
                VersionsManager.getInstance().move(currentVersion, childLeaf.getParentContainer());
            }
        }
    }

    /**
     * Binds a name to an object, along with associated attributes, overwriting any existing binding. If attrs is null and obj is a DirContext, the attributes from obj
     * are used. If attrs is null and obj is not a DirContext, any existing attributes associated with the object already bound in the directory remain unchanged. If
     * attrs is non-null, any existing attributes associated with the object already bound in the directory are removed and attrs is associated with the named object. If
     * obj is a DirContext and attrs is non-null, the attributes of obj are ignored.
     * 
     * @param name the name to bind; may not be empty
     * @param obj the object to bind; possibly null
     * @param attrs the attributes to associate with the binding
     * @exception InvalidAttributesException if some "mandatory" attributes of the binding are not supplied
     * @exception NamingException if a naming exception is encountered
     */
    @Override
    public void rebind(String name, Object obj, Attributes attrs) throws NamingException {

        // Note: No custom attributes allowed
        // Check obj type

        VFSItem vfsItem = resolveFile(name);
        if (vfsItem == null || (!(vfsItem instanceof VFSLeaf)))
            throw new NamingException(smgr.getString("resources.bindFailed", name));
        VFSLeaf file = (VFSLeaf) vfsItem;

        if (file instanceof Versionable && ((Versionable) file).getVersions().isVersioned()) {
            VersionsManager.getInstance().addToRevisions((Versionable) file, identity, "");
        }

        copyVFS(file, name, obj, attrs);
        if (file instanceof MetaTagged) {
            MetaInfo infos = ((MetaTagged) file).getMetaInfo();
            if (infos != null && infos.getAuthorIdentity() == null) {
                infos.setAuthor(userSession.getIdentity().getName());
                infos.clearThumbnails();
                infos.write();
            }
        }

        // used by move operations
        if (obj instanceof VFSResource) {
            VFSResource vfsResource = (VFSResource) obj;
            if (vfsResource.vfsItem instanceof Versionable
                    && ((Versionable) vfsResource.vfsItem).getVersions().isVersioned()) {
                Versionable currentVersion = (Versionable) vfsResource.vfsItem;
                VersionsManager.getInstance().move(currentVersion, file.getParentContainer());
            }
        }
    }

    private void copyVFS(VFSLeaf file, String name, Object obj, Attributes attrs) throws NamingException {
        InputStream is = null;
        if (obj instanceof Resource) {
            try {
                is = ((Resource) obj).streamContent();
            } catch (IOException e) {
                // ignore, check further
            }
        } else if (obj instanceof InputStream) {
            is = (InputStream) obj;
        } else if (obj instanceof DirContext) {
            createSubcontext(name, attrs);
            return;
        }
        if (is == null)
            throw new NamingException(smgr.getString("resources.bindFailed", name));

        // Try to get Quota
        long quotaLeft = -1;
        boolean withQuotaCheck = false;
        VFSContainer parentContainer = file.getParentContainer();
        if (parentContainer != null) {
            quotaLeft = VFSManager.getQuotaLeftKB(parentContainer);
            if (quotaLeft != Quota.UNLIMITED) {
                quotaLeft = quotaLeft * 1024; // convert from kB
                withQuotaCheck = true;
            } else {
                withQuotaCheck = false;
            }
        }
        // Open os
        OutputStream os = null;
        byte buffer[] = new byte[bufferSize];
        int len = -1;
        try {
            os = file.getOutputStream(false);
            while (true) {
                len = is.read(buffer);
                if (len == -1)
                    break;
                if (withQuotaCheck) {
                    // re-calculate quota and check
                    quotaLeft = quotaLeft - len;
                    if (quotaLeft < 0)
                        throw new NamingException("Quota exceeded.");
                }
                os.write(buffer, 0, len);
            }
        } catch (Exception e) {
            FileUtils.closeSafely(os); // close first, in order to be able to delete any reamins of the file
            file.delete();
            if (e instanceof NamingException)
                throw (NamingException) e;
            throw new NamingException(smgr.getString("resources.bindFailed"));
        } finally {
            FileUtils.closeSafely(os);
            FileUtils.closeSafely(is);
        }
    }

    /**
     * @see javax.naming.Context#createSubcontext(java.lang.String)
     */
    @Override
    public Context createSubcontext(String name) throws NamingException {
        return createSubcontext(name, null);
    }

    /**
     * Creates and binds a new context, along with associated attributes. This method creates a new subcontext with the given name, binds it in the target context (that
     * named by all but terminal atomic component of the name), and associates the supplied attributes with the newly created object. All intermediate and target contexts
     * must already exist. If attrs is null, this method is equivalent to Context.createSubcontext().
     * 
     * @param name the name of the context to create; may not be empty
     * @param attrs the attributes to associate with the newly created context
     * @return the newly created context
     * @exception NameAlreadyBoundException if the name is already bound
     * @exception InvalidAttributesException if attrs does not contain all the mandatory attributes required for creation
     * @exception NamingException if a naming exception is encountered
     */
    @Override
    public DirContext createSubcontext(String name, Attributes attrs) throws NamingException {

        VFSItem file = resolveFile(name);
        if (file != null)
            throw new NameAlreadyBoundException(smgr.getString("resources.alreadyBound", name));

        int lastSlash = name.lastIndexOf('/');
        if (lastSlash == -1)
            throw new NamingException();
        String parent = name.substring(0, lastSlash);
        VFSItem folder = resolveFile(parent);
        if (folder == null || (!(folder instanceof VFSContainer)))
            throw new NamingException(smgr.getString("resources.bindFailed", name));
        String newName = name.substring(lastSlash + 1);
        VFSItem childContainer = ((VFSContainer) folder).createChildContainer(newName);
        if (childContainer == null)
            throw new NamingException(smgr.getString("resources.bindFailed", name));
        return (DirContext) lookup(name);

    }

    /**
     * Retrieves the schema associated with the named object. The schema describes rules regarding the structure of the namespace and the attributes stored within it. The
     * schema specifies what types of objects can be added to the directory and where they can be added; what mandatory and optional attributes an object can have. The
     * range of support for schemas is directory-specific.
     * 
     * @param name the name of the object whose schema is to be retrieved
     * @return the schema associated with the context; never null
     * @exception OperationNotSupportedException if schema not supported
     * @exception NamingException if a naming exception is encountered
     */
    @Override
    public DirContext getSchema(String name) throws NamingException {
        throw new OperationNotSupportedException();
    }

    /**
     * Retrieves a context containing the schema objects of the named object's class definitions.
     * 
     * @param name the name of the object whose object class definition is to be retrieved
     * @return the DirContext containing the named object's class definitions; never null
     * @exception OperationNotSupportedException if schema not supported
     * @exception NamingException if a naming exception is encountered
     */
    @Override
    public DirContext getSchemaClassDefinition(String name) throws NamingException {
        throw new OperationNotSupportedException();
    }

    /**
     * Searches in a single context for objects that contain a specified set of attributes, and retrieves selected attributes. The search is performed using the default
     * SearchControls settings.
     * 
     * @param name the name of the context to search
     * @param matchingAttributes the attributes to search for. If empty or null, all objects in the target context are returned.
     * @param attributesToReturn the attributes to return. null indicates that all attributes are to be returned; an empty array indicates that none are to be returned.
     * @return a non-null enumeration of SearchResult objects. Each SearchResult contains the attributes identified by attributesToReturn and the name of the
     *         corresponding object, named relative to the context named by name.
     * @exception NamingException if a naming exception is encountered
     */
    @Override
    public NamingEnumeration<SearchResult> search(String name, Attributes matchingAttributes,
            String[] attributesToReturn) {
        return null;
    }

    /**
     * Searches in a single context for objects that contain a specified set of attributes. This method returns all the attributes of such objects. It is equivalent to
     * supplying null as the atributesToReturn parameter to the method search(Name, Attributes, String[]).
     * 
     * @param name the name of the context to search
     * @param matchingAttributes the attributes to search for. If empty or null, all objects in the target context are returned.
     * @return a non-null enumeration of SearchResult objects. Each SearchResult contains the attributes identified by attributesToReturn and the name of the
     *         corresponding object, named relative to the context named by name.
     * @exception NamingException if a naming exception is encountered
     */
    @Override
    public NamingEnumeration<SearchResult> search(String name, Attributes matchingAttributes) {
        return null;
    }

    /**
     * Searches in the named context or object for entries that satisfy the given search filter. Performs the search as specified by the search controls.
     * 
     * @param name the name of the context or object to search
     * @param filter the filter expression to use for the search; may not be null
     * @param cons the search controls that control the search. If null, the default search controls are used (equivalent to (new SearchControls())).
     * @return an enumeration of SearchResults of the objects that satisfy the filter; never null
     * @exception InvalidSearchFilterException if the search filter specified is not supported or understood by the underlying directory
     * @exception InvalidSearchControlsException if the search controls contain invalid settings
     * @exception NamingException if a naming exception is encountered
     */
    @Override
    public NamingEnumeration<SearchResult> search(String name, String filter, SearchControls cons) {
        return null;
    }

    /**
     * Searches in the named context or object for entries that satisfy the given search filter. Performs the search as specified by the search controls.
     * 
     * @param name the name of the context or object to search
     * @param filterExpr the filter expression to use for the search. The expression may contain variables of the form "{i}" where i is a nonnegative integer. May not be
     *            null.
     * @param filterArgs the array of arguments to substitute for the variables in filterExpr. The value of filterArgs[i] will replace each occurrence of "{i}". If null,
     *            equivalent to an empty array.
     * @param cons the search controls that control the search. If null, the default search controls are used (equivalent to (new SearchControls())).
     * @return an enumeration of SearchResults of the objects that satisy the filter; never null
     * @exception ArrayIndexOutOfBoundsException if filterExpr contains {i} expressions where i is outside the bounds of the array filterArgs
     * @exception InvalidSearchControlsException if cons contains invalid settings
     * @exception InvalidSearchFilterException if filterExpr with filterArgs represents an invalid search filter
     * @exception NamingException if a naming exception is encountered
     */
    @Override
    public NamingEnumeration<SearchResult> search(String name, String filterExpr, Object[] filterArgs,
            SearchControls cons) {
        return null;
    }

    // ------------------------------------------------------ Protected Methods

    /**
     * Resolve a file relative to this base. Make sure, paths are relative
     */
    protected VFSItem resolveFile(String name) {
        if (name == null)
            name = "";
        if (name.length() > 0 && name.charAt(0) == '/')
            name = name.substring(1);
        return base.resolve(name);
    }

    /**
     * List the resources which are members of a collection.
     * 
     * @param file Collection
     * @return Vector containg NamingEntry objects
     */
    protected List<NamingEntry> list(VFSItem file) {

        List<NamingEntry> entries = new ArrayList<NamingEntry>();

        if (!(file instanceof VFSContainer))
            return entries;
        List<VFSItem> children = ((VFSContainer) file).getItems();

        NamingEntry entry = null;
        for (VFSItem currentFile : children) {
            Object object;
            if (currentFile instanceof VFSContainer) {
                VFSDirContext tempContext = new VFSDirContext(env);
                tempContext.setVirtualDocBase(currentFile);
                object = tempContext;
            } else {
                object = new VFSResource(currentFile);
            }
            entry = new NamingEntry(currentFile.getName(), object, NamingEntry.ENTRY);
            entries.add(entry);
        }
        return entries;
    }

    // ----------------------------------------------- FileResource Inner Class

    /**
     * This specialized resource implementation avoids opening the IputStream to the file right away (which would put a lock on the file).
     */
    protected class VFSResource extends Resource {

        // -------------------------------------------------------- Constructor

        public VFSResource(VFSItem fileObject) {
            this.vfsItem = fileObject;
        }

        // --------------------------------------------------- Member Variables

        /**
         * Associated file object.
         */
        protected VFSItem vfsItem;

        /**
         * File length.
         */
        protected long length = -1L;

        // --------------------------------------------------- Resource Methods

        /**
         * Content accessor.
         * 
         * @return InputStream
         */
        @Override
        public InputStream streamContent() throws IOException {
            if (!(vfsItem instanceof VFSLeaf))
                throw new IOException("Can't get stream for VFSItem.");
            VFSLeaf vfsLeaf = (VFSLeaf) vfsItem;
            return vfsLeaf.getInputStream();
        }

    }

    // ------------------------------------- FileResourceAttributes Inner Class

    /**
     * This specialized resource attribute implementation does some lazy reading (to speed up simple checks, like checking the last modified date).
     */
    protected class VFSResourceAttributes extends ResourceAttributes {

        // -------------------------------------------------------- Constructor

        public VFSResourceAttributes(VFSItem vfsItem) {
            this.vfsItem = vfsItem;
        }

        // --------------------------------------------------- Member Variables

        protected VFSItem vfsItem;

        protected boolean accessed = false;

        protected String canonicalPath = null;

        // ----------------------------------------- ResourceAttributes Methods

        /**
         * Is collection.
         */
        @Override
        public boolean isCollection() {
            if (!accessed) {
                collection = (vfsItem instanceof VFSContainer);
            }
            return collection;
        }

        /**
         * Get content length.
         * 
         * @return content length value
         */
        @Override
        public long getContentLength() {
            if (contentLength != -1L)
                return contentLength;
            if (isCollection())
                contentLength = 0;
            else {
                VFSLeaf leaf = (VFSLeaf) vfsItem;
                contentLength = leaf.getSize();
            }
            return contentLength;
        }

        /**
         * Get creation time.
         * 
         * @return creation time value
         */
        @Override
        public long getCreation() {
            if (creation != -1L)
                return creation;
            if (isCollection())
                creation = 0;
            else {
                creation = vfsItem.getLastModified();
            }
            return creation;
        }

        /**
         * Get creation date.
         * 
         * @return Creation date value
         */
        @Override
        public Date getCreationDate() {
            return new Date(getCreation());
        }

        /**
         * Get last modified time.
         * 
         * @return lastModified time value
         */
        @Override
        public long getLastModified() {
            if (lastModified != -1L)
                return lastModified;
            if (isCollection())
                lastModified = 0;
            else {
                lastModified = vfsItem.getLastModified();
            }
            return lastModified;
        }

        /**
         * Get lastModified date.
         * 
         * @return LastModified date value
         */
        @Override
        public Date getLastModifiedDate() {
            return new Date(getLastModified());
        }

        /**
         * Get name.
         * 
         * @return Name value
         */
        @Override
        public String getName() {
            if (name == null)
                name = vfsItem.getName();
            return name;
        }

        /**
         * Get resource type.
         * 
         * @return String resource type
         */
        @Override
        public String getResourceType() {
            if (!accessed)
                isCollection(); // needed to initialize
            return super.getResourceType();
        }

    }

    protected class StringManager {

        public StringManager() {
            // dummy implementation
        }

        public String getString(String in) {
            return in;
        }

        public String getString(String in, String arg) {
            return in;
        }

    }

    /**
     * Represents a binding in a NamingContext.
     * 
     * @author Remy Maucherat
     */

    public class NamingEntry {

        // --------------------------------------------------------------
        // Constants

        public static final int ENTRY = 0;
        public static final int LINK_REF = 1;
        public static final int REFERENCE = 2;

        public static final int CONTEXT = 10;

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

        public NamingEntry(String name, Object value, int type) {
            this.name = name;
            this.value = value;
            this.type = type;
        }

        // ----------------------------------------------------- Instance Variables

        /**
         * The type instance variable is used to avoid unsing RTTI when doing lookups.
         */
        public int type;
        public String name;
        public Object value;

        // --------------------------------------------------------- Object Methods

        @Override
        public boolean equals(Object obj) {
            if ((obj != null) && (obj instanceof NamingEntry)) {
                return name.equals(((NamingEntry) obj).name);
            } else {
                return false;
            }
        }

        @Override
        public int hashCode() {
            return name.hashCode();
        }

    }

    /**
     * Naming enumeration implementation.
     * 
     * @author Remy Maucherat
     */

    public class NamingContextEnumeration implements NamingEnumeration<NameClassPair> {

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

        public NamingContextEnumeration(Iterator<NamingEntry> entries) {
            iterator = entries;
        }

        // -------------------------------------------------------------- Variables

        /**
         * Underlying enumeration.
         */
        protected Iterator<NamingEntry> iterator;

        // --------------------------------------------------------- Public Methods

        /**
         * Retrieves the next element in the enumeration.
         */
        @Override
        public NameClassPair next() {
            return nextElement();
        }

        /**
         * Determines whether there are any more elements in the enumeration.
         */
        @Override
        public boolean hasMore() {
            return iterator.hasNext();
        }

        /**
         * Closes this enumeration.
         */
        @Override
        public void close() {
            // nothing to do
        }

        @Override
        public boolean hasMoreElements() {
            return iterator.hasNext();
        }

        @Override
        public NameClassPair nextElement() {
            NamingEntry entry = iterator.next();
            return new NameClassPair(entry.name, entry.value.getClass().getName());
        }
    }

    public class NamingContextEnumeration2 implements NamingEnumeration<Binding> {

        private final NamingEnumeration<NameClassPair> entries;

        public NamingContextEnumeration2(NamingEnumeration<NameClassPair> entries) {
            this.entries = entries;
        }

        @Override
        public void close() {
            // nothing to do
        }

        @Override
        public boolean hasMore() throws NamingException {
            return entries.hasMore();
        }

        @Override
        public Binding next() throws NamingException {
            NameClassPair pair = entries.next();
            return new Binding(pair.getName(), pair.getClassName(), null);
        }

        @Override
        public boolean hasMoreElements() {
            return entries.hasMoreElements();
        }

        @Override
        public Binding nextElement() {
            try {
                return next();
            } catch (NamingException e) {
                e.printStackTrace();
                return null;
            }
        }
    }
}