org.sakaiproject.scorm.content.impl.ZipCHH.java Source code

Java tutorial

Introduction

Here is the source code for org.sakaiproject.scorm.content.impl.ZipCHH.java

Source

/**********************************************************************************
 * $URL:  $
 * $Id:  $
 ***********************************************************************************
 *
 * Copyright (c) 2007 The Sakai Foundation.
 * 
 * Licensed under the Educational Community License, Version 1.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at
 * 
 *      http://www.opensource.org/licenses/ecl1.php
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License.
 *
 **********************************************************************************/
package org.sakaiproject.scorm.content.impl;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.Serializable;
import java.util.LinkedList;
import java.util.List;

import javax.activation.MimetypesFileTypeMap;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.sakaiproject.content.api.ContentCollection;
import org.sakaiproject.content.api.ContentCollectionEdit;
import org.sakaiproject.content.api.ContentEntity;
import org.sakaiproject.content.api.ContentHostingHandler;
import org.sakaiproject.content.api.ContentHostingHandlerResolver;
import org.sakaiproject.content.api.ContentHostingService;
import org.sakaiproject.content.api.ContentResource;
import org.sakaiproject.content.api.ContentResourceEdit;
import org.sakaiproject.content.api.ResourceType;
import org.sakaiproject.content.api.ResourceTypeRegistry;
import org.sakaiproject.entity.api.Entity;
import org.sakaiproject.entity.api.EntityPropertyNotDefinedException;
import org.sakaiproject.entity.api.EntityPropertyTypeException;
import org.sakaiproject.entity.api.ResourceProperties;
import org.sakaiproject.entity.api.ResourcePropertiesEdit;
import org.sakaiproject.event.api.NotificationService;
import org.sakaiproject.exception.IdUnusedException;
import org.sakaiproject.exception.PermissionException;
import org.sakaiproject.exception.ServerOverloadException;
import org.sakaiproject.exception.TypeException;
import org.sakaiproject.scorm.content.api.Addable;
import org.sakaiproject.thread_local.cover.ThreadLocalManager;

public abstract class ZipCHH implements ContentHostingHandler, Addable, Serializable {
    /**
     * 
     */
    private static final long serialVersionUID = 507912123982364924L;

    private static final String REAL_PARENT_ENTITY_PROPERTY = "zipCHH@REAL_PARENT_ENTITY_ID";

    private static final String VIRTUAL_ZIP_ENTITY_PROPERTY = "zipCHH@IS_VIRTUAL_ZIP_ENTITY";

    private static final String ENTITY_CACHE_KEY = "zipCHHFindEntity@";

    private static final String LIST_CACHE_KEY = "zipCHHFindList@";

    private static Log log = LogFactory.getLog(ZipCHH.class);

    private ContentHostingHandlerResolver resolver;

    protected ResourceTypeRegistry resourceTypeRegistry;

    public void add(File file, String id) {
        try {
            ContentResource realParent = (ContentResource) getRealParent(id);
            byte[] archive = realParent.getContent();
            InputStream in = new ByteArrayInputStream(archive);
            ByteArrayOutputStream out = new ByteArrayOutputStream();

            ZipWriter writer = new ZipWriter(in, out);

            InputStream entryStream = new FileInputStream(file);

            String path = getRelativePath(realParent.getId(), id);
            newId(path, file.getName());

            writer.add(path + file.getName(), entryStream);
            writer.process();

            if (entryStream != null) {
                entryStream.close();
            }

            ContentResourceEdit realParentEdit = contentService().editResource(realParent.getId());
            realParentEdit.setContent(out.toByteArray());
            contentService().commitResource(realParentEdit, NotificationService.NOTI_NONE);
        } catch (Exception soe) {
            log.error("Caught an exception trying to add a resource", soe);
        }
    }

    protected void cacheEntity(ContentEntity ce) {
        if (null != ce) {
            ThreadLocalManager.set(ENTITY_CACHE_KEY + ce.getId(), ce);
        }
    }

    private void cacheList(String key, List<ContentEntity> list) {
        if (null != list) {
            List<String> idList = new LinkedList<String>();

            for (ContentEntity ce : list) {
                cacheEntity(ce);
                idList.add(ce.getId());
            }

            ThreadLocalManager.set(LIST_CACHE_KEY + key, idList);
        }
    }

    public void cancel(ContentCollectionEdit edit) {

    }

    public void cancel(ContentResourceEdit edit) {

    }

    public void commit(ContentCollectionEdit edit) {

    }

    public void commit(ContentResourceEdit edit) {
        if (edit.getVirtualContentEntity() == null) {
            try {
                contentService().commitResource(edit, NotificationService.NOTI_NONE);
            } catch (Exception e) {
                log.error("Caught an exception committing resource", e);
            }
        }
    }

    public void commitDeleted(ContentResourceEdit edit, String uuid) {
        try {
            ContentResource realParent = (ContentResource) getRealParent(edit);
            byte[] archive = realParent.getContent();
            InputStream in = new ByteArrayInputStream(archive);
            ByteArrayOutputStream out = new ByteArrayOutputStream();

            ZipWriter writer = new ZipWriter(in, out);

            String path = getRelativePath(realParent.getId(), edit.getId());
            writer.remove(path);
            writer.process();

            ContentResourceEdit realParentEdit = contentService().editResource(realParent.getId());
            realParentEdit.setContent(out.toByteArray());
            contentService().commitResource(realParentEdit, NotificationService.NOTI_NONE);
        } catch (Exception soe) {
            log.error("Caught an exception trying to delete a resource", soe);
        }
    }

    protected ContentHostingService contentService() {
        return null;
    }

    protected int countChildren(ContentEntity parent, int depth) {
        ContentResource realParent = (ContentResource) getRealParent(parent);

        byte[] archive = null;

        try {
            if (realParent != null) {
                archive = realParent.getContent();
            }
        } catch (ServerOverloadException soe) {
            log.error("Caught a server overload exception trying to grab real parent's content", soe);
        }

        if (archive == null || archive.length <= 0)
            return 0;

        final String relativePath = getRelativePath(realParent.getId(), parent.getId());

        ZipReader reader = new ZipReader(new ByteArrayInputStream(archive)) {

            @Override
            protected boolean includeContent(boolean isDirectory) {
                return !isDirectory;
            }

            @Override
            protected boolean isValid(String entryPath) {
                if (entryPath.endsWith(Entity.SEPARATOR) && entryPath.length() > 1) {
                    entryPath = entryPath.substring(0, entryPath.length() - 1);
                }

                return isDeepEnough(entryPath, relativePath, 1);
            }

            @Override
            protected ContentEntity processEntry(String entryPath, ByteArrayOutputStream outStream,
                    boolean isDirectory) {
                return null;
            }
        };

        reader.read();

        return reader.getCount();
    }

    protected List<ContentEntity> extractChildren(ContentEntity parent, int depth) {
        final ContentResource realParent = (ContentResource) getRealParent(parent);

        byte[] archive = null;

        try {
            if (null != realParent) {
                archive = realParent.getContent();
            }
        } catch (ServerOverloadException soe) {
            log.error("Caught a server overload exception trying to grab real parent's content", soe);
        }

        if (archive == null || archive.length <= 0)
            return null;

        final String relativePath = getRelativePath(realParent.getId(), parent.getId());

        ZipReader reader = new ZipReader(new ByteArrayInputStream(archive)) {

            @Override
            protected boolean includeContent(boolean isDirectory) {
                return !isDirectory;
            }

            @Override
            protected boolean isValid(String entryPath) {
                if (entryPath.endsWith(Entity.SEPARATOR) && entryPath.length() > 1) {
                    entryPath = entryPath.substring(0, entryPath.length() - 1);
                }

                return isDeepEnough(entryPath, relativePath, 1);
            }

            @Override
            protected ContentEntity processEntry(String entryPath, ByteArrayOutputStream outStream,
                    boolean isDirectory) {
                ContentEntity entity = null;

                if (entryPath.endsWith(Entity.SEPARATOR) && entryPath.length() > 1) {
                    entryPath = entryPath.substring(0, entryPath.length() - 1);
                }

                if (isDirectory) {
                    entity = makeCollection(realParent, entryPath, null);
                } else {
                    // We don't need content stored for these objects 
                    entity = makeResource(realParent, entryPath, null, null);
                }

                return entity;
            }

        };

        @SuppressWarnings("unchecked")
        List<ContentEntity> list = reader.read();

        return list;
    }

    private byte[] extractContent(String id) throws ServerOverloadException {
        ContentResource resource = (ContentResource) resolveId(id);

        if (null != resource)
            return resource.getContent();

        return null;
    }

    protected ContentEntity extractEntity(ContentEntity realEntity, final String path)
            throws ServerOverloadException {
        final ContentResource cr = (ContentResource) realEntity;

        byte[] archive = cr.getContent();

        if (archive == null || archive.length <= 0)
            return null;

        ZipReader reader = new ZipReader(new ByteArrayInputStream(archive)) {

            @Override
            protected boolean includeContent(boolean isDirectory) {
                return !isDirectory;
            }

            @Override
            protected boolean isValid(String entryPath) {
                return entryPath.equals(path);
            }

            @Override
            protected ContentEntity processEntry(String entryPath, ByteArrayOutputStream outStream,
                    boolean isDirectory) {
                if (isDirectory)
                    return makeCollection(cr, path, null);

                return makeResource(cr, path, null, outStream.toByteArray());
            }

        };

        return (ContentEntity) reader.readFirst();
    }

    protected List<ContentEntity> findChildren(ContentEntity ce, int depth) throws ServerOverloadException {

        List<ContentEntity> list = uncacheList(ce.getId());

        if (null == list) {
            list = extractChildren(ce, depth);
            cacheList(ce.getId(), list);
        }
        return list;
    }

    public List getCollections(ContentCollection collection) {

        List<ContentEntity> resources = null;
        try {
            resources = findChildren(collection, 1);
        } catch (ServerOverloadException soe) {
            log.error("Caught a server overload exception trying to extract resources from zip", soe);
        }

        List<ContentEntity> collections = new LinkedList<ContentEntity>();

        if (null != resources) {
            for (ContentEntity ce : resources) {
                if (ce.isCollection()) {
                    collections.add(ce);
                }
            }
        }
        return collections;
    }

    public ContentCollectionEdit getContentCollectionEdit(String id) {
        ContentEntity ce = resolveId(id);

        if (ce instanceof ContentCollectionEdit)
            return (ContentCollectionEdit) ce;

        return null;
    }

    public abstract String getContentHostingHandlerName();

    public ContentResourceEdit getContentResourceEdit(String id) {
        ContentEntity ce = resolveId(id);

        if (ce instanceof ContentResourceEdit)
            return (ContentResourceEdit) ce;

        return null;
    }

    public List getFlatResources(ContentEntity ce) {
        List resourceIds = new LinkedList();

        List<ContentEntity> members = null;
        try {
            members = findChildren(ce, -1);
        } catch (Exception e) {
            log.error("Caught an exception ", e);
        }

        if (members != null) {
            for (ContentEntity member : members) {
                resourceIds.add(member.getId());
            }
        }

        return resourceIds;
    }

    public int getMemberCount(ContentEntity ce) {
        return countChildren(ce, 1);
    }

    protected ContentEntity getRealEntity(String id) {
        ContentEntity ce = null;

        try {
            ce = contentService().getCollection(id);
        } catch (IdUnusedException iue) {
            // The whole point is not to throw this.
        } catch (TypeException te) {
            // This doesn't seem to get thrown even though the API suggests it should
        } catch (PermissionException pe) {
            log.error("Caught a permission exception trying to find the real entity of " + id, pe);
        }

        if (ce == null) {
            try {
                ce = contentService().getResource(id);
            } catch (IdUnusedException iue) {
                // The whole point is not to throw this.
            } catch (TypeException te) {
                // This shouldn't happen.
            } catch (PermissionException pe) {
                log.error("Caught a permission exception trying to find the real entity of " + id, pe);
            }
        }

        if (null != ce) {
            ResourceProperties props = ce.getProperties();

            try {
                if (props.getBooleanProperty(VIRTUAL_ZIP_ENTITY_PROPERTY)) {
                    ce = null;
                }
            } catch (EntityPropertyNotDefinedException epnde) {
                // This will be thrown each time we look up a real resource rather than a virtual one
            } catch (EntityPropertyTypeException epnde) {
                log.warn("This entity property is not of type boolean " + VIRTUAL_ZIP_ENTITY_PROPERTY);
            }
        }

        return ce;
    }

    protected ContentEntity getRealParent(ContentEntity ce) {
        ResourceProperties props = ce.getProperties();

        String id = null;
        if (null != props) {
            try {
                id = (String) props.get(REAL_PARENT_ENTITY_PROPERTY);
            } catch (Exception e) {
                log.debug("Caught an unimportant exception getting a property that might not be there: "
                        + e.getMessage());
            }
        }

        if (null != id)
            return getRealEntity(id);

        // If that method doesn't work, then fall through to the other one.
        return getRealParent(ce.getId());
    }

    protected ContentEntity getRealParent(String id) {
        ContentEntity ce = null;

        ce = getRealEntity(id);

        if (ce == null) {
            if (id.equals(Entity.SEPARATOR))
                return getRealParent(Entity.SEPARATOR);
            int lastSlash = id.lastIndexOf(Entity.SEPARATOR);
            if (lastSlash > 0) {
                String parentId = id.substring(0, lastSlash);
                ce = getRealParent(parentId);
            }
        }

        return ce;
    }

    protected String getRelativePath(String parentId, String finalId) {
        String path = "";

        if (finalId.startsWith(parentId) && finalId.length() > parentId.length() + 1) {
            path = finalId.substring(parentId.length() + 1);
        }

        return path;
    }

    public ContentHostingHandlerResolver getResolver() {
        return resolver;
    }

    public byte[] getResourceBody(ContentResource resource) throws ServerOverloadException {

        return extractContent(resource.getId());
    }

    public List getResources(ContentCollection collection) {
        List<ContentEntity> items = null;
        try {
            items = findChildren(collection, 1);
        } catch (ServerOverloadException soe) {
            log.error("Caught a server overload exception trying to extract resources from zip", soe);
        }

        List<ContentEntity> resources = new LinkedList<ContentEntity>();

        if (null != items) {
            for (ContentEntity ce : items) {
                if (!ce.isCollection()) {
                    resources.add(ce);
                }
            }
        }

        return resources;
    }

    public ResourceTypeRegistry getResourceTypeRegistry() {
        return resourceTypeRegistry;
    }

    public ContentEntity getVirtualContentEntity(ContentEntity ce, String finalId) {
        ContentEntity virtualEntity = null;

        if (null == ce)
            return null;

        ResourceProperties realProperties = ce.getProperties();
        if (null == realProperties)
            return null;

        /*String chhbeanname = realProperties.getProperty(ContentHostingHandlerResolver.CHH_BEAN_NAME);
            
        if (chhbeanname == null || !chhbeanname.equals(getContentHostingHandlerName()))
           return getRealEntity(ce.getId());*/

        String parentId = ce.getId();
        String path = getRelativePath(parentId, finalId);

        if (path.length() == 0) {
            // Grab some data from the real content entity
            String name = (String) realProperties.get(ResourceProperties.PROP_DISPLAY_NAME);
            virtualEntity = makeCollection(ce, path, name);

            virtualEntity.setContentHandler(this);
            //ce.setContentHandler(this);
            ce.setVirtualContentEntity(virtualEntity);
            // This is stupid, but I think it needs to be set since BaseContentService checks to verify that this value is not null or else it gets into an infinite loop.
            //virtualEntity.setVirtualContentEntity(ce);
        } else {
            virtualEntity = uncacheEntity(newId(parentId, path));

            if (null == virtualEntity) {
                try {
                    virtualEntity = extractEntity(ce, path);
                    cacheEntity(virtualEntity);
                } catch (Exception e) {
                    log.error("Caught an exception extracting resource ", e);
                }
            }
        }

        return virtualEntity;
    }

    public void init() {
        //resourceTypeRegistry.register(new ZipCollectionType());
        //resourceTypeRegistry.register(new CompressedResourceType());
    }

    protected boolean isDeepEnough(String zipPath, String path, int depth) {
        int howDeep = 1;

        if (path.length() > 0) {
            String[] pFields = path.split(Entity.SEPARATOR);

            if (null != pFields) {
                depth += pFields.length;
            }

            if (!zipPath.startsWith(path))
                return false;
        }

        String[] fields = zipPath.split(Entity.SEPARATOR);

        if (fields != null) {
            howDeep = fields.length;
        }

        return howDeep == depth;
    }

    protected ContentCollectionEdit makeCollection(ContentEntity ce, String path, String name) {
        String[] fields = path.split(Entity.SEPARATOR);

        if (null == name) {
            name = "[error]";

            if (null != fields && fields.length >= 1) {
                // Grab the last item as a name for this collection
                name = fields[fields.length - 1];
            }
        }
        String newId = newId(ce.getId(), path);
        if (!newId.endsWith(Entity.SEPARATOR)) {
            newId += Entity.SEPARATOR;
        }

        ContentCollectionEdit collection = (ContentCollectionEdit) resolver.newCollectionEdit(newId);

        ResourcePropertiesEdit props = collection.getPropertiesEdit();
        props.addProperty(ResourceProperties.PROP_DISPLAY_NAME, name);
        props.addProperty(ContentHostingHandlerResolver.CHH_BEAN_NAME, getContentHostingHandlerName());
        props.addProperty(ResourceProperties.PROP_IS_COLLECTION, "true");
        props.addProperty(VIRTUAL_ZIP_ENTITY_PROPERTY, "true");
        props.addProperty(REAL_PARENT_ENTITY_PROPERTY, ce.getId());

        //collection.setVirtualContentEntity(collection);
        collection.setResourceType(ZipCollectionType.ZIP_COLLECTION_TYPE_ID);
        collection.setContentHandler(this);

        return collection;
    }

    protected ContentResourceEdit makeResource(ContentEntity ce, String path, String name, byte[] content) {
        String[] fields = path.split(Entity.SEPARATOR);

        if (null == name) {
            name = "[error]";

            if (null != fields && fields.length >= 1) {
                // Grab the last item as a name for this collection
                name = fields[fields.length - 1];
            }
        }

        ContentResourceEdit resource = (ContentResourceEdit) resolver.newResourceEdit(newId(ce.getId(), path));

        if (null != content) {
            resource.setContent(content);
            resource.setContentLength(content.length);
        }

        resource.setResourceType(ResourceType.TYPE_HTML);
        resource.setContentType(new MimetypesFileTypeMap().getContentType(name));

        ResourcePropertiesEdit props = resource.getPropertiesEdit();
        props.addProperty(ResourceProperties.PROP_DISPLAY_NAME, name);
        props.addProperty(ContentHostingHandlerResolver.CHH_BEAN_NAME, getContentHostingHandlerName());
        props.addProperty(VIRTUAL_ZIP_ENTITY_PROPERTY, "true");
        props.addProperty(REAL_PARENT_ENTITY_PROPERTY, ce.getId());

        //resource.setVirtualContentEntity(resource);

        resource.setContentHandler(this);

        return resource;
    }

    protected String newId(String id, String path) {
        StringBuffer buffer = new StringBuffer();
        if (id.endsWith(Entity.SEPARATOR)) {
            if (path.startsWith(Entity.SEPARATOR)) {
                path = path.substring(1);
            }
            buffer.append(id).append(path);
        } else {
            if (path.startsWith(Entity.SEPARATOR)) {
                buffer.append(id).append(path);
            } else {
                buffer.append(id).append(Entity.SEPARATOR).append(path);
            }
        }

        return buffer.toString();
    }

    public ContentResourceEdit putDeleteResource(String id, String uuid, String userId) {
        return (ContentResourceEdit) resolveId(id);
    }

    public void removeCollection(ContentCollectionEdit edit) {
        ContentEntity pe = getRealParent(edit.getId());

        if (pe != null) {
            try {
                contentService().removeResource(pe.getId());
            } catch (Exception e) {
                log.error("Unable to remove the underlying resource", e);
            }
        }
    }

    public void removeResource(ContentResourceEdit edit) {
        ContentEntity pe = getRealParent(edit.getId());

        if (pe != null) {
            try {
                contentService().removeResource(pe.getId());
            } catch (Exception e) {
                log.error("Unable to remove the underlying resource", e);
            }
        }
    }

    private ContentEntity resolveId(String id) {
        ContentEntity ce = null;

        ContentEntity pe = getRealParent(id);
        if (null != pe) {
            ce = getVirtualContentEntity(pe, id);
        }
        return ce;
    }

    public void setResolver(ContentHostingHandlerResolver resolver) {
        this.resolver = resolver;
    }

    public void setResourceTypeRegistry(ResourceTypeRegistry registry) {
        resourceTypeRegistry = registry;
    }

    public InputStream streamResourceBody(ContentResource resource) throws ServerOverloadException {

        InputStream stream = new ByteArrayInputStream(resource.getContent());

        return stream;
    }

    /*
     * These cache/uncache methods bind objects to the 'current' request -- since the entire zip stream
     * would be read through several times per request otherwise, I think it makes sense to do this.
     */
    protected ContentEntity uncacheEntity(String key) {
        ContentEntity ce = null;
        try {
            ce = (ContentEntity) ThreadLocalManager.get(ENTITY_CACHE_KEY + key);
        } catch (ClassCastException e) {
            log.error("Caught a class cast exception finding resource with key " + key, e);
        }

        return ce;
    }

    private List<ContentEntity> uncacheList(String key) {
        List<ContentEntity> list = null;
        List<String> idList = null;

        try {
            idList = (List<String>) ThreadLocalManager.get(LIST_CACHE_KEY + key);

            if (null != idList) {
                list = new LinkedList<ContentEntity>();
                for (String id : idList) {
                    ContentEntity ce = uncacheEntity(id);

                    if (null != ce) {
                        list.add(ce);
                    }
                }
            }
        } catch (ClassCastException e) {
            log.error("Caught a class cast exception finding id list with key " + key, e);
        }

        return list;
    }

}