<commit_before>/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.curriki.xwiki.plugin.asset;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.util.*;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.collections.ListUtils;
import org.apache.velocity.VelocityContext;
import org.curriki.xwiki.plugin.asset.attachment.*;
import org.curriki.xwiki.plugin.asset.composite.CollectionCompositeAsset;
import org.curriki.xwiki.plugin.asset.composite.FolderCompositeAsset;
import org.curriki.xwiki.plugin.asset.composite.RootCollectionCompositeAsset;
import org.curriki.xwiki.plugin.asset.other.InvalidAsset;
import org.curriki.xwiki.plugin.asset.other.ProtectedAsset;
import org.curriki.xwiki.plugin.asset.text.TextAssetManager;
import org.curriki.xwiki.plugin.asset.text.TextAsset;
import org.curriki.xwiki.plugin.mimetype.MimeTypePlugin;

import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.plugin.fileupload.FileUploadPlugin;
import com.xpn.xwiki.api.Document;
import com.xpn.xwiki.api.Property;
import com.xpn.xwiki.doc.XWikiAttachment;
import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.doc.XWikiLock;
import com.xpn.xwiki.objects.BaseObject;
import com.xpn.xwiki.objects.BaseStringProperty;
import com.xpn.xwiki.api.Object;
import com.xpn.xwiki.api.Attachment;

public class Asset extends CurrikiDocument {
    private static final Log LOG = LogFactory.getLog(Asset.class);


    public Asset(XWikiDocument doc, XWikiContext context) {
        super(doc, context);
    }


    /**
     * Provide a title valid to insert in a javascript call in a tag onclick
     * @return
     */
    public String getJSTitle() {
        return Util.escapeForJS(getDisplayTitle());
    }

    /**
     * Provide a title valid to insert in a javascript call in a tag onclick
     * with a title length limitation
     * @param length
     * @return
     */
    public String getJSTitle(int length) {
        return Util.escapeForJS(getDisplayTitle(length));
    }

    /**
     * Provide the document fullname escaped for inclusing in a js call in a tab onclick
     * @return
     */
    public String getJSFullName() {
        return Util.escapeForJS(getFullName());
    }

    /**
     * Provided a truncated title to the length specified
     * @param length
     * @return
     */
    public String getDisplayTitle(int length) {
        String title = getDisplayTitle();
        if (title.length()>length) {
         title = title.substring(0,length);
         title = title + "...";
        }
        return title;
    }


    /**
     * Return a display call without the {pre} tags to be usable in plain vm templates
     * @param fieldname
     * @param mode
     * @param nopre
     * @return
     */
    public String display(String fieldname, String mode, boolean nopre) {
        String result = super.display(fieldname, mode);
        if (nopre) {
            return result.replaceAll("\\{/?pre\\}","");
        } else
            return result;
    }

    public String getCategory() {
        if (hasA(Constants.ASSET_CLASS)) {
            Object obj = getObject(Constants.ASSET_CLASS);
            use(obj);
            String category = (String) getValue(Constants.ASSET_CLASS_CATEGORY);
            if (category==null)
                return Constants.ASSET_CATEGORY_UNKNOWN;
            else
                return category;
        } else {
            return Constants.ASSET_CATEGORY_UNKNOWN;
        }
    }

    public String getFiletype() {
        String className = getActiveClass();
        try {
            use(Constants.ATTACHMENT_ASSET_CLASS);
            return (String) getValue(Constants.ATTACHMENT_ASSET_FILE_TYPE);
        } finally {
            if (className!=null)
                use(className);
        }
    }

    public String getFiletypeClass() {
        String filetype = getFiletype();
        if ((filetype==null)||("".equals(filetype)))
            return "unknown";
        else
            return filetype;
    }

    public String getCategoryClass() {
        String className = getActiveClass();
        String category = getCategory();

        if (Constants.ASSET_CATEGORY_COLLECTION.equals(category)) {
            try {
                use(Constants.COMPOSITE_ASSET_CLASS);
                String colltype = (String) getValue(Constants.COMPOSITE_ASSET_CLASS_TYPE);
                // in case it is a folder we want folder and not collection
                if (Constants.COMPOSITE_ASSET_CLASS_TYPE_SUBFOLDER.equals(colltype)) {
                    return "folder";
                }
            } finally {
                if (className!=null)
                    use(className);
            }
        }

        if (Constants.ASSET_CATEGORY_ATTACHMENT.equals(category)) {
            // it the attachment is unknown or empty we want "attachment-unknown" and not "attachment"
            if ("unknown".equals(getFiletypeClass()))
                return "attachment-unknown";
        }

        if ((category==null)||("".equals(category)))
            return "unknown";
        else
            return category;
    }

    public void setCategory(String category) {
        BaseObject obj = getDoc().getObject(Constants.ASSET_CLASS, true, context);
        obj.setStringValue(Constants.ASSET_CLASS_CATEGORY, category);
    }

    /**
     * Gets the category sub-type (file type in the case of attachments)
     *
     * This should be overridden in each of the sub-classes of Asset
     *
     * @return category sub-type
     */
    public String getCategorySubtype() {
        Asset asset;

        try {
            asset = subclassAs(getAssetClass());
            // If we didn't get a subtype then return unknown subtype
            if (asset.getClass().getSimpleName().equals("Asset")) {
                return Constants.ASSET_CATEGORY_SUBTYPE_UNKNOWN;
            }
            return asset.getCategorySubtype();
        } catch (XWikiException e) {
            return Constants.ASSET_CATEGORY_SUBTYPE_UNKNOWN;
        }
    }

    /**
     * This functions will display the asset including a fallback system
     * For a specific mode. This function can be overidden for a specific asset type
     * Otherwise it will use a default rule system to find the appropriate template
     * @return
     */
    public String displayAsset(String mode) {
        String result = "";
        Asset asset = null;

        // we should subclass the asset to have access
        // to more functions
        try {
            asset = subclassAs(getAssetClass());
        } catch (XWikiException e) {
            asset = this;
        }

        java.lang.Object previousAsset = null;
        VelocityContext vcontext = null;
        try {
            vcontext = (VelocityContext) context.get("vcontext");
            previousAsset = vcontext.get("asset");
            vcontext.put("asset", asset);
            // run the displayer
            result = asset.displayAssetTemplate(mode);
        } finally {
            if (vcontext !=null) {
                if (previousAsset==null)
                    vcontext.remove("asset");
                else
                    vcontext.put("asset", previousAsset);
            }
        }
        return result;
    }

    /**
     * This functions will display the asset including a fallback system
     * For a specific mode. This function can be overidden for a specific asset type
     * Otherwise it will use a default rule system to find the appropriate template
     * @return
     */
    protected String displayAssetTemplate(String mode) {
        MimeTypePlugin mimePlugin = getMimeTypePlugin();
        String category = getCategory();
        String displayer = mimePlugin.getDisplayer(category, null, context);
        String result = context.getWiki().parseTemplate("assets/displayers/" + displayer  + "_" + mode + ".vm", context);
        if (result.equals(""))
           result =  context.getWiki().parseTemplate("assets/displayers/" + category + "_" + mode + ".vm", context);
        if (result.equals(""))
           result =  context.getWiki().parseTemplate("assets/displayers/" + mode + ".vm", context);
        return result;
    }
        
    public void saveDocument(String comment) throws XWikiException {
        saveDocument(comment, false);
    }

    public static Asset createTempAsset(String parentAsset, XWikiContext context) throws XWikiException {
        return createTempAsset(parentAsset, null, context);
    }

    public static Asset createTempAsset(String parentAsset, String publishSpace, XWikiContext context) throws XWikiException {
        if (Constants.GUEST_USER.equals(context.getUser())) {
            throw new AssetException(AssetException.ERROR_ASSET_FORBIDDEN, "XWikiGuest cannot create assets.");
        }

        String pageName = context.getWiki().getUniquePageName(Constants.ASSET_TEMPORARY_SPACE, context);

        XWikiDocument newDoc = context.getWiki().getDocument(Constants.ASSET_TEMPORARY_SPACE, pageName, context);

        Asset assetDoc = new Asset(newDoc, context);
        assetDoc.init(parentAsset, publishSpace);
        assetDoc.saveDocument(context.getMessageTool().get("curriki.comment.createnewsourceasset"), true);

        return assetDoc;
    }

    public static Asset copyTempAsset(String copyOf, XWikiContext context) throws XWikiException {
        return copyTempAsset(copyOf, null, context);
    }

    public static Asset copyTempAsset(String copyOf, String publishSpace, XWikiContext context) throws XWikiException {
        if (Constants.GUEST_USER.equals(context.getUser())) {
            throw new AssetException(AssetException.ERROR_ASSET_FORBIDDEN, "XWikiGuest cannot create assets.");
        }

        Asset copyDoc = fetchAsset(copyOf, context);
        if (!copyDoc.assertCanDuplicate()) {
            throw new AssetException(AssetException.ERROR_ASSET_FORBIDDEN, "Source resource cannot be copied");
        }


        String pageName = context.getWiki().getUniquePageName(Constants.ASSET_TEMPORARY_SPACE, context);

        XWikiDocument newDoc = copyDoc.getDoc().copyDocument(Constants.ASSET_TEMPORARY_SPACE+"."+pageName, context);

        Asset assetDoc = new Asset(newDoc, context);
        //assetDoc.init(copyOf, publishSpace);
        assetDoc.getDoc().setCreator(context.getUser());
        assetDoc.getDoc().setCustomClass(assetDoc.getClass().getName());

        // Remove comments from copied asset
        assetDoc.removeObjects("XWiki.XWikiComments");

        // Remove inherited CRS-reviews from copied asset
        assetDoc.removeObjects(Constants.ASSET_CURRIKI_REVIEW_CLASS);
        assetDoc.removeObjects(Constants.ASSET_CURRIKI_REVIEW_STATUS_CLASS);

        BaseObject newLicenceObj = assetDoc.getDoc().getObject(Constants.ASSET_LICENCE_CLASS);
        if (newLicenceObj==null) {
            newLicenceObj = assetDoc.getDoc().newObject(Constants.ASSET_LICENCE_CLASS, context);
        }

        // Rights Holder should be by default the pretty name of the user added with the current rights holder (only if not already in the list)
        String newRightsHolder = context.getWiki().getLocalUserName(context.getUser(), null, false, context);
        String origRightsHolder = copyDoc.getDoc().getStringValue(Constants.ASSET_LICENCE_CLASS, Constants.ASSET_LICENCE_ITEM_RIGHTS_HOLDER);
	if (!origRightsHolder.matches("\\b"+newRightsHolder+"\\b")) {
		newRightsHolder += ", " + origRightsHolder;
		newLicenceObj.setStringValue(Constants.ASSET_LICENCE_ITEM_RIGHTS_HOLDER, newRightsHolder);
	} else {
		newLicenceObj.setStringValue(Constants.ASSET_LICENCE_ITEM_RIGHTS_HOLDER, origRightsHolder);
	}

        BaseObject newObjAsset = assetDoc.getDoc().getObject(Constants.ASSET_CLASS);
        if (newObjAsset==null) {
            newObjAsset = assetDoc.getDoc().newObject(Constants.ASSET_CLASS, context);
        }
        // Keep the information allowing to track where that asset came from
        newObjAsset.setStringValue(Constants.ASSET_CLASS_TRACKING, copyOf);


        // Clear rights objects otherwise this will trigger a remove object although these have never been saved
        assetDoc.getDoc().setObjects("XWiki.XWikiRights", new Vector<BaseObject>());

        assetDoc.applyRightsPolicy();

        //assetDoc.saveDocument(context.getMessageTool().get("curriki.comment.copiedsourceasset"), true);
        context.getWiki().saveDocument(assetDoc.getDoc(), context.getMessageTool().get("curriki.comment.copiedsourceasset"), true, context);
        return assetDoc;
    }

    protected void init(String parentAsset) throws XWikiException {
        init(parentAsset, null);
    }

    protected void init(String parentAsset, String publishSpace) throws XWikiException {
        assertCanEdit();
        getDoc().setCreator(context.getUser());

        inheritMetadata(parentAsset, publishSpace);

        getDoc().setCustomClass(getClass().getName());
        setDefaultContent();

        applyRightsPolicy();
    }

    protected void setDefaultContent() throws XWikiException {
        assertCanEdit();
        getDoc().setContent("");
    }

    protected void initSubType() throws XWikiException {
        assertCanEdit();
        // Empty for Superclass
    }

    public void addAttachment(InputStream iStream, String name) throws XWikiException, IOException {
        assertCanEdit();
        XWikiAttachment att = addAttachment(name, iStream).getAttachment();
        getDoc().saveAttachmentContent(att, context);
    }

    public String getDisplayTitle() {
        String title = getTitle();
        return (title == null || title.length() == 0) ? Constants.ASSET_DISPLAYTITLE_UNTITLED : title;
    }

    public String getDescription() {
        String className = getActiveClass();

        use(Constants.ASSET_CLASS);
        String description = (String) getValue(Constants.ASSET_CLASS_DESCRIPTION);

        if (className != null) {
            use(className);
        }

        return (description == null || description.length() == 0) ? "" : description;
    }

    public void changeOwnership(String newUser) {
        if (hasProgrammingRights()) {
            XWikiDocument assetDoc = getDoc();
            assetDoc.setCreator(newUser);
        }
    }

    /**
     * Set the rights objects based on the current right setting
     *
     * @throws XWikiException
     */
    public void applyRightsPolicy() throws XWikiException {
        applyRightsPolicy(null);
    }

    /**
     * Set the rights object based on the right in param or the current right setting if null
     *
     * @param right
     * @throws XWikiException
     */
    public void applyRightsPolicy(String right) throws XWikiException {
        XWikiDocument assetDoc = getDoc();
        assetDoc.removeObjects("XWiki.XWikiRights");

        BaseObject assetObj = assetDoc.getObject(Constants.ASSET_CLASS);
        String rights;

        if (right == null) {
            // Use existing rights value
            rights = assetObj.getStringValue(Constants.ASSET_CLASS_RIGHT);
            LOG.debug("applyRightsPolicy:  retrieved: '"+right+"'");
        } else {
            LOG.debug("applyRightsPolicy:  passed: '"+right+"'");
            rights = right;
            assetObj.setStringValue(Constants.ASSET_CLASS_RIGHT, right);
        }

        // Make sure rights value is valid, default to PUBLIC if not
        if (rights == null
            || !(rights.equals(Constants.ASSET_CLASS_RIGHT_PUBLIC)
                 || rights.equals(Constants.ASSET_CLASS_RIGHT_MEMBERS)
                 || rights.equals(Constants.ASSET_CLASS_RIGHT_PRIVATE))) {
            LOG.warn("Rights is being defaulted.  Got: '"+rights+"'");
            rights = Constants.ASSET_CLASS_RIGHT_PUBLIC;
            assetObj.setStringValue(Constants.ASSET_CLASS_RIGHT, rights);
        }

        // Rights
        BaseObject rightObj;

        // If collection is user
        String usergroupfield = Constants.RIGHTS_CLASS_USER;
        String uservalue =  ("".equals(assetDoc.getCreator())) ? context.getUser() : assetDoc.getCreator();
        String usergroupvalue = uservalue;

        // If collection is group
        if (assetDoc.getSpace().startsWith(Constants.GROUP_COLLECTION_SPACE_PREFIX)) {
            usergroupfield = Constants.RIGHTS_CLASS_GROUP;
            usergroupvalue = assetDoc.getSpace().substring(5) + ".MemberGroup";
        }

        // Always let the admin group have edit access
        rightObj = assetDoc.newObject(Constants.RIGHTS_CLASS, context);
        rightObj.setLargeStringValue(Constants.RIGHTS_CLASS_GROUP, Constants.RIGHTS_ADMIN_GROUP);
        rightObj.setStringValue("levels", "edit");
        rightObj.setIntValue("allow", 1);

        // Always let the creator/group edit
        rightObj = assetDoc.newObject(Constants.RIGHTS_CLASS, context);
        // CURRIKI-2468 - allow creator to always edit/view their creations
        if (usergroupfield.equals(Constants.RIGHTS_CLASS_GROUP)) {
            rightObj.setLargeStringValue(Constants.RIGHTS_CLASS_USER, uservalue);
        }
        rightObj.setLargeStringValue(usergroupfield, usergroupvalue);
        rightObj.setStringValue("levels", "edit");
        rightObj.setIntValue("allow", 1);

        if (rights.equals(Constants.ASSET_CLASS_RIGHT_PUBLIC)) {
            // Viewable by all and any member can edit
            rightObj = assetDoc.newObject(Constants.RIGHTS_CLASS, context);
            rightObj.setLargeStringValue(Constants.RIGHTS_CLASS_GROUP, Constants.RIGHTS_ALL_GROUP);
            rightObj.setStringValue("levels", "edit");
            rightObj.setIntValue("allow", 1);
        } else if (rights.equals(Constants.ASSET_CLASS_RIGHT_MEMBERS)) {
            // Viewable by all, only user/group can edit
        } else {
            // rights == private, so only allow creator/group to view and edit
            rightObj = assetDoc.newObject(Constants.RIGHTS_CLASS, context);
            // CURRIKI-2468 - allow creator to always edit/view their creations
            if (usergroupfield.equals(Constants.RIGHTS_CLASS_GROUP)) {
                rightObj.setLargeStringValue(Constants.RIGHTS_CLASS_USER, uservalue);
            }
            rightObj.setLargeStringValue(usergroupfield, usergroupvalue);
            rightObj.setStringValue("levels", "view");
            rightObj.setIntValue("allow", 1);

            // TODO: something here caused CURRIKI-3100 so the rest of the metadata could not get saved
            //private assets must be removed from review queue
            removeFromReviewQueue();
        }
    }

    public Class<? extends Asset> getAssetClass() {
        if (hasAccessLevel("view")) {
            if (isFolder()){
                if (isRootCollection()){
                    // Note that the Root Collection doesn't have an Asset Class
                    //  so it has to come before checking if no ASSET_CLASS
                    return RootCollectionCompositeAsset.class;
                } else if (isCollection()){
                    return CollectionCompositeAsset.class;
                } else {
                    return FolderCompositeAsset.class;
                }
            }

            String category = getCategory();
            if (category == null) {
                return InvalidAsset.class;
            }

            // call the sub type asset manager to get more details
            AssetManager assetManager = (AssetManager) DefaultAssetManager.getAssetSubTypeManager(category);
            if (assetManager!=null) {
                return assetManager.getAssetClass();
            } else {
                // Last is just an attachment item
                if (doc.getAttachmentList().size() > 0) {
                    return AttachmentAsset.class;
                }
                return Asset.class;
            }
        } else {
            return ProtectedAsset.class;
        }
    }

    public String getAssetType() {
        return getAssetClass().getSimpleName().replaceAll("Asset$", "");
    }

    public boolean isFolder() {
        if (doc.getObjectNumbers(Constants.COMPOSITE_ASSET_CLASS) == 0){
            return false;
        } else {
            // Work around a bug XWIKI-1624
            // TODO: Remove the work-around once XWIKI-1624 is fixed
            List<BaseObject> compObjs = doc.getObjects(Constants.COMPOSITE_ASSET_CLASS);
            for (BaseObject compObj : compObjs){
                if (compObj != null){
                    return true;
                }
            }
        }
        return false;
    }

    public boolean isCollection() {
        if (isFolder()) {
            com.xpn.xwiki.api.Object obj = getObject(Constants.COMPOSITE_ASSET_CLASS);
            if (getValue(Constants.COMPOSITE_ASSET_CLASS_TYPE, obj).equals(Constants.COMPOSITE_ASSET_CLASS_TYPE_COLLECTION)) {
                return true;
            }
        }
        return false;
    }

    public boolean isRootCollection() {
        if (isFolder()) {
            com.xpn.xwiki.api.Object obj = getObject(Constants.COMPOSITE_ASSET_CLASS);
            if (getValue(Constants.COMPOSITE_ASSET_CLASS_TYPE, obj).equals(Constants.COMPOSITE_ASSET_CLASS_TYPE_ROOT_COLLECTION)) {
                return true;
            }
        }
        return false;
    }


    static public Asset fetchAsset(String assetName, XWikiContext context) throws XWikiException {
        com.xpn.xwiki.api.XWiki xwikiApi = new com.xpn.xwiki.api.XWiki(context.getWiki(), context);
        Document doc = xwikiApi.getDocument(assetName);

        if (doc instanceof Asset){
            return ((Asset) doc).as(null);
        } else {
            throw new AssetException(AssetException.ERROR_ASSET_NOT_FOUND, "Asset "+assetName+" could not be found");
        }
    }

    static public Asset fetchAsset(String web, String page, XWikiContext context) throws XWikiException {
        return Asset.fetchAsset(web+"."+page, context);
    }

    public List<Property> getMetadata() {
        List<Property> md = new ArrayList<Property>();

        com.xpn.xwiki.api.Object assetObj = getObject(Constants.ASSET_CLASS);
        for (java.lang.Object prop : assetObj.getPropertyNames()) {
            LOG.debug("Adding "+prop+" to metadata list");
            md.add(assetObj.getProperty((String) prop));
        }

        com.xpn.xwiki.api.Object licenseObj = getObject(Constants.ASSET_LICENCE_CLASS);
        for (java.lang.Object prop : licenseObj.getPropertyNames()) {
            LOG.debug("Adding "+prop+" to metadata list");
            md.add(licenseObj.getProperty((String) prop));
        }

        // Add title now that it isn't in the asset object
        BaseStringProperty baseProp = new BaseStringProperty();
        baseProp.setName("title");
        baseProp.setValue(getTitle());
        Property prop = new Property(baseProp, context);
        md.add(prop);

        // Add creator
        baseProp = new BaseStringProperty();
        baseProp.setName("creator");
        baseProp.setValue(getCreator());
        prop = new Property(baseProp, context);
        md.add(prop);

        // Add creator's name
        String creatorName =  context.getWiki().getLocalUserName(context.getUser(), null, false, context);
        baseProp = new BaseStringProperty();
        baseProp.setName("creatorName");
        baseProp.setValue(creatorName);
        prop = new Property(baseProp, context);
        md.add(prop);

        // Add page's name
        baseProp = new BaseStringProperty();
        baseProp.setName("assetpage");
        baseProp.setValue(getFullName());
        prop = new Property(baseProp, context);
        md.add(prop);

        // Add rights List
        Map<String,Boolean> rightsList = getRightsList();
        String rightsListString = "{";
        for (String right : rightsList.keySet()) {
            if (rightsListString.length()>1) {
                rightsListString += ",";
            }
            rightsListString += "\""+right+"\":"+(rightsList.get(right)?"true":"false");
        }
        rightsListString += "}";

        baseProp = new BaseStringProperty();
        baseProp.setName("rightsList");
        baseProp.setValue(rightsListString);
        prop = new Property(baseProp, context);
        md.add(prop);

        // Add subcategory
        baseProp = new BaseStringProperty();
        baseProp.setName("subcategory");
        baseProp.setValue(getCategorySubtype());
        prop = new Property(baseProp, context);
        md.add(prop);

        // Add assetType to metadata
        Class assetType = getAssetClass();
        baseProp = new BaseStringProperty();
        baseProp.setName("fullAssetType");
        baseProp.setValue(assetType.getCanonicalName());
        prop = new Property(baseProp, context);
        md.add(prop);

        // And add shortAssetType to metadata
        String shortAssetType = assetType.getSimpleName();
        if (!shortAssetType.equals("Asset")) {
            shortAssetType = getAssetType();
        }
        baseProp = new BaseStringProperty();
        baseProp.setName("assetType");
        baseProp.setValue(shortAssetType);
        prop = new Property(baseProp, context);
        md.add(prop);

        baseProp = new BaseStringProperty();
        baseProp.setName("revision");
        baseProp.setValue(getVersion());
        prop = new Property(baseProp, context);
        md.add(prop);

        return md;
    }

    public <A extends Asset> A as(Class<? extends Asset> wantedClass) throws XWikiException {
        return (A) as(wantedClass, false);
    }

    public <A extends Asset> A subclassAs(Class<? extends Asset> wantedClass) throws XWikiException {
        return (A) this.as(wantedClass, true);
    }

    public <A extends Asset> A as(Class<? extends Asset> wantedClass, boolean moreSpecific) throws XWikiException {
        // If this is already a subtype of the wanted class then just return this
        if (wantedClass != null && wantedClass.isAssignableFrom(this.getClass())) {
            return (A) this;
        }

        Class returnClass;

        // Make sure the determined type is a subclass of the wanted type
        Class<? extends Asset> assetType = getAssetClass();
        if (!moreSpecific) {
            if (wantedClass == null) {
                // Return as whatever subtype it is
                returnClass = assetType;
            } else if (wantedClass.isAssignableFrom(assetType)) {
                // Return as the desired type
                returnClass = wantedClass;
            } else {
                throw new AssetException(AssetException.ERROR_ASSET_INCOMPATIBLE, "Document of type "+assetType+" cannot become "+wantedClass);
            }
        } else {
            // Subclass an Asset
            if (assetType.equals(wantedClass)) {
                // Already is that type, so don't initialize
                moreSpecific = false;
                returnClass = wantedClass;
            } else if (assetType.equals(Asset.class) && assetType.isAssignableFrom(wantedClass)) {
                // Turn into a specified subtype of Asset (initializing the subtype)
                returnClass = wantedClass;
            } else {
                throw new AssetException(AssetException.ERROR_ASSET_INCOMPATIBLE, "Document of type "+assetType+" cannot become "+wantedClass);
            }
        }

        Class[] parameterTypes = new Class[2];
        parameterTypes[0] = XWikiDocument.class;
        parameterTypes[1] = XWikiContext.class;

        java.lang.Object[] initargs = new java.lang.Object[2];
        initargs[0] = doc;
        initargs[1] = context;

        A subtyped;
        try {
            Constructor<A> constructor = returnClass.getConstructor(parameterTypes);
            subtyped = constructor.newInstance(initargs);
            if (moreSpecific) {
                subtyped.initSubType();
            }
        } catch (Exception e) {
            throw new AssetException(AssetException.ERROR_ASSET_INCOMPATIBLE, "Document of type "+assetType+" cannot become "+wantedClass, e);
        }

        return subtyped;
    }

    public void inheritMetadata() throws XWikiException {
        inheritMetadata(null, null);
    }

    public void inheritMetadata(String parentAsset) throws XWikiException {
        inheritMetadata(parentAsset, null);
    }

    public void inheritMetadata(String parentAsset, String publishSpace) throws XWikiException {
        assertCanEdit();
        XWikiDocument assetDoc = getDoc();

        BaseObject assetObj = assetDoc.getObject(Constants.ASSET_CLASS);
        if (assetObj == null) {
            assetObj = assetDoc.getObject(Constants.ASSET_CLASS, true, context);
        }

	boolean licenceSet = false;
        BaseObject newLicenceObj = doc.getObject(Constants.ASSET_LICENCE_CLASS);
        if (newLicenceObj == null) {
            newLicenceObj = doc.newObject(Constants.ASSET_LICENCE_CLASS, context);
        }

        // CURRIKI-2451 - Make sure group rights are used by default
        if (publishSpace != null && publishSpace.startsWith(Constants.GROUP_COLLECTION_SPACE_PREFIX)) {
            String groupSpace = publishSpace.replaceFirst("^"+Constants.GROUP_COLLECTION_SPACE_PREFIX, Constants.GROUP_SPACE_PREFIX);
            String rights = Constants.ASSET_CLASS_RIGHT_PUBLIC;

            // TODO: This should probably be using the SpaceManager extension
            XWikiDocument groupSpaceDoc = context.getWiki().getDocument(groupSpace+"."+Constants.GROUP_RIGHTS_PAGE, context);
            if (groupSpaceDoc != null){
                // Note that the values for the group access defaults
                //  DO NOT MATCH the values that need to be applied to the collection
                BaseObject rObj = groupSpaceDoc.getObject(Constants.GROUP_RIGHTS_CLASS);
                if (rObj != null){
                    String groupDefaultPrivs = rObj.getStringValue(Constants.GROUP_RIGHTS_PROPERTY);
                    if (groupDefaultPrivs.equals(Constants.GROUP_RIGHT_PRIVATE)){
                        rights = Constants.ASSET_CLASS_RIGHT_PRIVATE;
                    } else if (groupDefaultPrivs.equals(Constants.GROUP_RIGHT_PROTECTED)){
                        rights = Constants.ASSET_CLASS_RIGHT_MEMBERS;
                    } else if (groupDefaultPrivs.equals(Constants.GROUP_RIGHT_PUBLIC)){
                        rights = Constants.ASSET_CLASS_RIGHT_PUBLIC;
                    }


                    // CURRIKI-4377 - inherit grade level, topic, language, and licence from group
                    if (parentAsset == null || parentAsset.length() == 0) {
                        // educationLevel
                        List edLevels = rObj.getListValue(Constants.GROUP_DEFAULT_GRADE_PROPERTY);
                        assetObj.setDBStringListValue(Constants.ASSET_CLASS_EDUCATIONAL_LEVEL, edLevels);

                        // topic
                        List topics = rObj.getListValue(Constants.GROUP_DEFAULT_TOPIC_PROPERTY);
                        assetObj.setDBStringListValue(Constants.ASSET_CLASS_FRAMEWORK_ITEMS, topics);

                        // language
                        String lang = rObj.getStringValue(Constants.GROUP_DEFAULT_LANGUAGE_PROPERTY);
                        assetObj.setStringValue(Constants.ASSET_CLASS_LANGUAGE, lang);

                        // licence
                        String licence = rObj.getStringValue(Constants.GROUP_DEFAULT_LICENCE_PROPERTY);
                        newLicenceObj.setStringValue(Constants.ASSET_LICENCE_ITEM_LICENCE_TYPE, licence);
			licenceSet = true;
                    }
                }
            }

            assetObj.setStringValue(Constants.ASSET_CLASS_RIGHT, rights);
        }

        XWikiDocument parentDoc = null;
        if (parentAsset != null && parentAsset.length() > 0) {
            parentDoc = context.getWiki().getDocument(parentAsset, context);
            if (parentDoc.isNew()) {
                throw new AssetException(AssetException.MODULE_PLUGIN_ASSET, AssetException.ERROR_ASSET_NOT_FOUND, "Parent asset not found");
            }
        }
        // the Root collection does not have a proper asset class so we can't inherit from it
        if (parentDoc != null && parentDoc.getObject(Constants.ASSET_CLASS) != null && !parentDoc.getName().equals(Constants.ROOT_COLLECTION_PAGE)) {
            BaseObject parentAssetObj = (BaseObject) parentDoc.getObject(Constants.ASSET_CLASS).clone();

            copyProperty(parentAssetObj, assetObj, Constants.ASSET_CLASS_EDUCATIONAL_LEVEL);
            copyProperty(parentAssetObj, assetObj, Constants.ASSET_CLASS_FRAMEWORK_ITEMS);
            copyProperty(parentAssetObj, assetObj, Constants.ASSET_CLASS_RIGHT);
            copyProperty(parentAssetObj, assetObj, Constants.ASSET_CLASS_KEYWORDS);
            copyProperty(parentAssetObj, assetObj, Constants.ASSET_CLASS_LANGUAGE);
        }

        // make sure default rights value is not empty (default to public)
        String rights = assetObj.getStringValue(Constants.ASSET_CLASS_RIGHT);
        if ((rights==null)||(rights.equals(""))) {
            assetObj.setStringValue(Constants.ASSET_CLASS_RIGHT, Constants.ASSET_CLASS_RIGHT_PUBLIC);
        }

        // the Root collection does not have an asset Licence class
        if (parentDoc != null && parentDoc.getObject(Constants.ASSET_LICENCE_CLASS) != null) {
            BaseObject parentLicenceObjAsset = parentDoc.getObject(Constants.ASSET_LICENCE_CLASS);
            copyProperty(parentLicenceObjAsset, newLicenceObj, Constants.ASSET_LICENCE_ITEM_LICENCE_TYPE);
        } else if (!licenceSet) {
            newLicenceObj.setStringValue(Constants.ASSET_LICENCE_ITEM_LICENCE_TYPE, Constants.ASSET_LICENCE_ITEM_LICENCE_TYPE_DEFAULT);
        }

        // Rights holder should be by default the pretty name of the user
        newLicenceObj.setStringValue(Constants.ASSET_LICENCE_ITEM_RIGHTS_HOLDER, context.getWiki().getLocalUserName(context.getUser(), null, false, context));
    }

    /**
     * Make a folder in the current asset
     * @return
     * @throws XWikiException
     */
    public FolderCompositeAsset makeFolder() throws XWikiException {
        return makeFolder(null);
    }

    /**
     * Make a folder in the current asset
     * @param page
     * @return
     * @throws XWikiException
     */
    public FolderCompositeAsset makeFolder(String page) throws XWikiException {
        assertCanEdit();
        FolderCompositeAsset asset = subclassAs(FolderCompositeAsset.class);

        if (page != null) {
            asset.addSubasset(page);
        }
        saveDocument(context.getMessageTool().get("curriki.comment.createfoldersourceasset"), true);
        return asset;
    }

    /**
     * Make a collection in the current asset
     * @return
     * @throws XWikiException
     */
    public CollectionCompositeAsset makeCollection() throws XWikiException {
        return makeCollection(null);
    }

    /**
     * Make a composite collection in the current asset
     * @param page
     * @return
     * @throws XWikiException
     */
    public CollectionCompositeAsset makeCollection(String page) throws XWikiException {
        assertCanEdit();
        CollectionCompositeAsset asset = subclassAs(CollectionCompositeAsset.class);

        if (page != null) {
            asset.addSubasset(page);
        }
        saveDocument(context.getMessageTool().get("curriki.comment.createfoldersourceasset"), true);
        return asset;
    }

    /**
     * Make an attachment asset based on the type of attachment
     * This will call the appropriate subtype asset manager for further processing
     * @return an AttachmentAsset object
     * @throws XWikiException
     */
    public AttachmentAsset processAttachment() throws XWikiException {
        assertCanEdit();
        AttachmentAsset asset = subclassAs(AttachmentAsset.class);

        if (doc.getAttachmentList().size() > 0) {
            XWikiAttachment attach = doc.getAttachmentList().get(0);
            determineFileTypeAndCategory(attach);
            saveDocument(context.getMessageTool().get("curriki.comment.createtextsourceasset"), true);
        }

        return asset;
    }

    /**
     * This functions determines the file type and the category based on an attachment
     * @param attachment
     * @throws XWikiException
     */
    protected void determineFileTypeAndCategory(XWikiAttachment attachment) throws XWikiException {
        String filename = attachment.getFilename();
        String extension = (filename.lastIndexOf(".") != -1 ? filename.substring(filename.lastIndexOf(".") + 1).toLowerCase(): null);
        MimeTypePlugin mimePlugin = getMimeTypePlugin();
        String filetype =  mimePlugin.getFileType(extension, context);
        String category = mimePlugin.getCategory(filetype, context);
        XWikiDocument assetDoc = getDoc();

        // set the attachment information
        BaseObject documentObject = assetDoc.getObject(Constants.ATTACHMENT_ASSET_CLASS, true, context);
        documentObject.setStringValue(Constants.ATTACHMENT_ASSET_FILE_TYPE, filetype);
        documentObject.setLongValue(Constants.ATTACHMENT_ASSET_FILE_SIZE, attachment.getFilesize());

        // set the category
        BaseObject assetObj = assetDoc.getObject(Constants.ASSET_CLASS, true, context);
        assetObj.setStringValue(Constants.ASSET_CLASS_CATEGORY, category);

        // call the sub type asset manager to get more details
        AssetManager assetManager = DefaultAssetManager.getAssetSubTypeManager(category);
        if (assetManager!=null) {
            assetManager.updateSubAssetClass(assetDoc, filetype, category, attachment, context);
        }
    }

    protected MimeTypePlugin getMimeTypePlugin() {
        MimeTypePlugin mimePlugin = (MimeTypePlugin) context.getWiki().getPlugin(MimeTypePlugin.PLUGIN_NAME, context);
        return mimePlugin;
    }

    public Boolean isPublished() {
        return !getSpace().equals("AssetTemp");
    }

    public Asset publish(String space) throws XWikiException {
        return publish(space, true);
    }

    public Asset publish(String space, String name) throws XWikiException {
        return publish(space, name, true);
    }

    public Asset publish(String space, boolean checkSpace) throws XWikiException {
        String prettyName = context.getWiki().clearName(getTitle(), true, true, context);

        return publish(space, prettyName, checkSpace);
    }

    public Asset publish(String space, String name, boolean checkSpace) throws XWikiException {
        if (isPublished()) {
            throw new AssetException("This resource is already published");
        }

        assertCanEdit();

        XWikiDocument assetDoc = getDoc();
        // Be sure we have a category first -- attachments can't get them automatically yet
        BaseObject obj = assetDoc.getObject(Constants.ASSET_CLASS);
        if (obj != null) {
            String category = obj.getStringValue(Constants.ASSET_CLASS_CATEGORY);
            if (category == null || category.length() == 0 || category.equals(Constants.ASSET_CATEGORY_UNKNOWN)) {
                processAttachment();
            }
        }

        if (!space.startsWith(Constants.COLLECTION_PREFIX)) {
            throw new AssetException("You cannot publish to the space "+space);
        }

        if (!validate()) {
            throw new AssetException("Validation failed.");
        }

        if (checkSpace) {
            CollectionSpace.ensureExists(space, context);
        }

        // Let's choose a nice name for the page
        String prettyName = context.getWiki().clearName(name, true, true, context);
        assetDoc.rename(space + "." + context.getWiki().getUniquePageName(space, prettyName.trim(), context), new ArrayList<String>(), context);

        applyRightsPolicy();

        List<String> params = new ArrayList<String>();
        params.add(assetDoc.getStringValue(Constants.ASSET_CLASS_CATEGORY));
        try {
          save(context.getMessageTool().get("curriki.comment.finishcreatingsubasset", params));
        } catch (Exception e) {
          // THIS IS A HACK to fix creating an attachment based asset with core 2.x
          // we need to find the real issue
          // retry the saving as a temp workaround
          save(context.getMessageTool().get("curriki.comment.finishcreatingsubasset", params));
        }
        if (isCollection()) {
            RootCollectionCompositeAsset root = CollectionSpace.getRootCollection(space, context);
            root.addSubasset(this.getFullName());
            root.saveDocument(context.getMessageTool().get("curriki.comment.addtocollectionasset"));
        }

        return this;
    }

    public boolean validate() throws XWikiException {
        // Has the asset been subtyped ?
        if (getAssetClass().equals(Asset.class)) {
            throw new AssetException("This asset is not complete.");
        }

        // Other validation

        // Super's validate
        return super.validate();<commit_msg>CURRIKI-5174 rename raised errors in hibernate since it saved the document, moved the code to copyDocument and manually save<commit_after>/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.curriki.xwiki.plugin.asset;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.util.*;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.collections.ListUtils;
import org.apache.velocity.VelocityContext;
import org.curriki.xwiki.plugin.asset.attachment.*;
import org.curriki.xwiki.plugin.asset.composite.CollectionCompositeAsset;
import org.curriki.xwiki.plugin.asset.composite.FolderCompositeAsset;
import org.curriki.xwiki.plugin.asset.composite.RootCollectionCompositeAsset;
import org.curriki.xwiki.plugin.asset.other.InvalidAsset;
import org.curriki.xwiki.plugin.asset.other.ProtectedAsset;
import org.curriki.xwiki.plugin.asset.text.TextAssetManager;
import org.curriki.xwiki.plugin.asset.text.TextAsset;
import org.curriki.xwiki.plugin.mimetype.MimeTypePlugin;

import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.plugin.fileupload.FileUploadPlugin;
import com.xpn.xwiki.api.Document;
import com.xpn.xwiki.api.Property;
import com.xpn.xwiki.doc.XWikiAttachment;
import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.doc.XWikiLock;
import com.xpn.xwiki.objects.BaseObject;
import com.xpn.xwiki.objects.BaseStringProperty;
import com.xpn.xwiki.api.Object;
import com.xpn.xwiki.api.Attachment;

public class Asset extends CurrikiDocument {
    private static final Log LOG = LogFactory.getLog(Asset.class);


    public Asset(XWikiDocument doc, XWikiContext context) {
        super(doc, context);
    }


    /**
     * Provide a title valid to insert in a javascript call in a tag onclick
     * @return
     */
    public String getJSTitle() {
        return Util.escapeForJS(getDisplayTitle());
    }

    /**
     * Provide a title valid to insert in a javascript call in a tag onclick
     * with a title length limitation
     * @param length
     * @return
     */
    public String getJSTitle(int length) {
        return Util.escapeForJS(getDisplayTitle(length));
    }

    /**
     * Provide the document fullname escaped for inclusing in a js call in a tab onclick
     * @return
     */
    public String getJSFullName() {
        return Util.escapeForJS(getFullName());
    }

    /**
     * Provided a truncated title to the length specified
     * @param length
     * @return
     */
    public String getDisplayTitle(int length) {
        String title = getDisplayTitle();
        if (title.length()>length) {
         title = title.substring(0,length);
         title = title + "...";
        }
        return title;
    }


    /**
     * Return a display call without the {pre} tags to be usable in plain vm templates
     * @param fieldname
     * @param mode
     * @param nopre
     * @return
     */
    public String display(String fieldname, String mode, boolean nopre) {
        String result = super.display(fieldname, mode);
        if (nopre) {
            return result.replaceAll("\\{/?pre\\}","");
        } else
            return result;
    }

    public String getCategory() {
        if (hasA(Constants.ASSET_CLASS)) {
            Object obj = getObject(Constants.ASSET_CLASS);
            use(obj);
            String category = (String) getValue(Constants.ASSET_CLASS_CATEGORY);
            if (category==null)
                return Constants.ASSET_CATEGORY_UNKNOWN;
            else
                return category;
        } else {
            return Constants.ASSET_CATEGORY_UNKNOWN;
        }
    }

    public String getFiletype() {
        String className = getActiveClass();
        try {
            use(Constants.ATTACHMENT_ASSET_CLASS);
            return (String) getValue(Constants.ATTACHMENT_ASSET_FILE_TYPE);
        } finally {
            if (className!=null)
                use(className);
        }
    }

    public String getFiletypeClass() {
        String filetype = getFiletype();
        if ((filetype==null)||("".equals(filetype)))
            return "unknown";
        else
            return filetype;
    }

    public String getCategoryClass() {
        String className = getActiveClass();
        String category = getCategory();

        if (Constants.ASSET_CATEGORY_COLLECTION.equals(category)) {
            try {
                use(Constants.COMPOSITE_ASSET_CLASS);
                String colltype = (String) getValue(Constants.COMPOSITE_ASSET_CLASS_TYPE);
                // in case it is a folder we want folder and not collection
                if (Constants.COMPOSITE_ASSET_CLASS_TYPE_SUBFOLDER.equals(colltype)) {
                    return "folder";
                }
            } finally {
                if (className!=null)
                    use(className);
            }
        }

        if (Constants.ASSET_CATEGORY_ATTACHMENT.equals(category)) {
            // it the attachment is unknown or empty we want "attachment-unknown" and not "attachment"
            if ("unknown".equals(getFiletypeClass()))
                return "attachment-unknown";
        }

        if ((category==null)||("".equals(category)))
            return "unknown";
        else
            return category;
    }

    public void setCategory(String category) {
        BaseObject obj = getDoc().getObject(Constants.ASSET_CLASS, true, context);
        obj.setStringValue(Constants.ASSET_CLASS_CATEGORY, category);
    }

    /**
     * Gets the category sub-type (file type in the case of attachments)
     *
     * This should be overridden in each of the sub-classes of Asset
     *
     * @return category sub-type
     */
    public String getCategorySubtype() {
        Asset asset;

        try {
            asset = subclassAs(getAssetClass());
            // If we didn't get a subtype then return unknown subtype
            if (asset.getClass().getSimpleName().equals("Asset")) {
                return Constants.ASSET_CATEGORY_SUBTYPE_UNKNOWN;
            }
            return asset.getCategorySubtype();
        } catch (XWikiException e) {
            return Constants.ASSET_CATEGORY_SUBTYPE_UNKNOWN;
        }
    }

    /**
     * This functions will display the asset including a fallback system
     * For a specific mode. This function can be overidden for a specific asset type
     * Otherwise it will use a default rule system to find the appropriate template
     * @return
     */
    public String displayAsset(String mode) {
        String result = "";
        Asset asset = null;

        // we should subclass the asset to have access
        // to more functions
        try {
            asset = subclassAs(getAssetClass());
        } catch (XWikiException e) {
            asset = this;
        }

        java.lang.Object previousAsset = null;
        VelocityContext vcontext = null;
        try {
            vcontext = (VelocityContext) context.get("vcontext");
            previousAsset = vcontext.get("asset");
            vcontext.put("asset", asset);
            // run the displayer
            result = asset.displayAssetTemplate(mode);
        } finally {
            if (vcontext !=null) {
                if (previousAsset==null)
                    vcontext.remove("asset");
                else
                    vcontext.put("asset", previousAsset);
            }
        }
        return result;
    }

    /**
     * This functions will display the asset including a fallback system
     * For a specific mode. This function can be overidden for a specific asset type
     * Otherwise it will use a default rule system to find the appropriate template
     * @return
     */
    protected String displayAssetTemplate(String mode) {
        MimeTypePlugin mimePlugin = getMimeTypePlugin();
        String category = getCategory();
        String displayer = mimePlugin.getDisplayer(category, null, context);
        String result = context.getWiki().parseTemplate("assets/displayers/" + displayer  + "_" + mode + ".vm", context);
        if (result.equals(""))
           result =  context.getWiki().parseTemplate("assets/displayers/" + category + "_" + mode + ".vm", context);
        if (result.equals(""))
           result =  context.getWiki().parseTemplate("assets/displayers/" + mode + ".vm", context);
        return result;
    }
        
    public void saveDocument(String comment) throws XWikiException {
        saveDocument(comment, false);
    }

    public static Asset createTempAsset(String parentAsset, XWikiContext context) throws XWikiException {
        return createTempAsset(parentAsset, null, context);
    }

    public static Asset createTempAsset(String parentAsset, String publishSpace, XWikiContext context) throws XWikiException {
        if (Constants.GUEST_USER.equals(context.getUser())) {
            throw new AssetException(AssetException.ERROR_ASSET_FORBIDDEN, "XWikiGuest cannot create assets.");
        }

        String pageName = context.getWiki().getUniquePageName(Constants.ASSET_TEMPORARY_SPACE, context);

        XWikiDocument newDoc = context.getWiki().getDocument(Constants.ASSET_TEMPORARY_SPACE, pageName, context);

        Asset assetDoc = new Asset(newDoc, context);
        assetDoc.init(parentAsset, publishSpace);
        assetDoc.saveDocument(context.getMessageTool().get("curriki.comment.createnewsourceasset"), true);

        return assetDoc;
    }

    public static Asset copyTempAsset(String copyOf, XWikiContext context) throws XWikiException {
        return copyTempAsset(copyOf, null, context);
    }

    public static Asset copyTempAsset(String copyOf, String publishSpace, XWikiContext context) throws XWikiException {
        if (Constants.GUEST_USER.equals(context.getUser())) {
            throw new AssetException(AssetException.ERROR_ASSET_FORBIDDEN, "XWikiGuest cannot create assets.");
        }

        Asset copyDoc = fetchAsset(copyOf, context);
        if (!copyDoc.assertCanDuplicate()) {
            throw new AssetException(AssetException.ERROR_ASSET_FORBIDDEN, "Source resource cannot be copied");
        }


        String pageName = context.getWiki().getUniquePageName(Constants.ASSET_TEMPORARY_SPACE, context);

        XWikiDocument newDoc = copyDoc.getDoc().copyDocument(Constants.ASSET_TEMPORARY_SPACE+"."+pageName, context);

        Asset assetDoc = new Asset(newDoc, context);
        //assetDoc.init(copyOf, publishSpace);
        assetDoc.getDoc().setCreator(context.getUser());
        assetDoc.getDoc().setCustomClass(assetDoc.getClass().getName());

        // Remove comments from copied asset
        assetDoc.removeObjects("XWiki.XWikiComments");

        // Remove inherited CRS-reviews from copied asset
        assetDoc.removeObjects(Constants.ASSET_CURRIKI_REVIEW_CLASS);
        assetDoc.removeObjects(Constants.ASSET_CURRIKI_REVIEW_STATUS_CLASS);

        BaseObject newLicenceObj = assetDoc.getDoc().getObject(Constants.ASSET_LICENCE_CLASS);
        if (newLicenceObj==null) {
            newLicenceObj = assetDoc.getDoc().newObject(Constants.ASSET_LICENCE_CLASS, context);
        }

        // Rights Holder should be by default the pretty name of the user added with the current rights holder (only if not already in the list)
        String newRightsHolder = context.getWiki().getLocalUserName(context.getUser(), null, false, context);
        String origRightsHolder = copyDoc.getDoc().getStringValue(Constants.ASSET_LICENCE_CLASS, Constants.ASSET_LICENCE_ITEM_RIGHTS_HOLDER);
	if (!origRightsHolder.matches("\\b"+newRightsHolder+"\\b")) {
		newRightsHolder += ", " + origRightsHolder;
		newLicenceObj.setStringValue(Constants.ASSET_LICENCE_ITEM_RIGHTS_HOLDER, newRightsHolder);
	} else {
		newLicenceObj.setStringValue(Constants.ASSET_LICENCE_ITEM_RIGHTS_HOLDER, origRightsHolder);
	}

        BaseObject newObjAsset = assetDoc.getDoc().getObject(Constants.ASSET_CLASS);
        if (newObjAsset==null) {
            newObjAsset = assetDoc.getDoc().newObject(Constants.ASSET_CLASS, context);
        }
        // Keep the information allowing to track where that asset came from
        newObjAsset.setStringValue(Constants.ASSET_CLASS_TRACKING, copyOf);


        // Clear rights objects otherwise this will trigger a remove object although these have never been saved
        assetDoc.getDoc().setObjects("XWiki.XWikiRights", new Vector<BaseObject>());

        assetDoc.applyRightsPolicy();

        //assetDoc.saveDocument(context.getMessageTool().get("curriki.comment.copiedsourceasset"), true);
        context.getWiki().saveDocument(assetDoc.getDoc(), context.getMessageTool().get("curriki.comment.copiedsourceasset"), true, context);
        return assetDoc;
    }

    protected void init(String parentAsset) throws XWikiException {
        init(parentAsset, null);
    }

    protected void init(String parentAsset, String publishSpace) throws XWikiException {
        assertCanEdit();
        getDoc().setCreator(context.getUser());

        inheritMetadata(parentAsset, publishSpace);

        getDoc().setCustomClass(getClass().getName());
        setDefaultContent();

        applyRightsPolicy();
    }

    protected void setDefaultContent() throws XWikiException {
        assertCanEdit();
        getDoc().setContent("");
    }

    protected void initSubType() throws XWikiException {
        assertCanEdit();
        // Empty for Superclass
    }

    public void addAttachment(InputStream iStream, String name) throws XWikiException, IOException {
        assertCanEdit();
        XWikiAttachment att = addAttachment(name, iStream).getAttachment();
        getDoc().saveAttachmentContent(att, context);
    }

    public String getDisplayTitle() {
        String title = getTitle();
        return (title == null || title.length() == 0) ? Constants.ASSET_DISPLAYTITLE_UNTITLED : title;
    }

    public String getDescription() {
        String className = getActiveClass();

        use(Constants.ASSET_CLASS);
        String description = (String) getValue(Constants.ASSET_CLASS_DESCRIPTION);

        if (className != null) {
            use(className);
        }

        return (description == null || description.length() == 0) ? "" : description;
    }

    public void changeOwnership(String newUser) {
        if (hasProgrammingRights()) {
            XWikiDocument assetDoc = getDoc();
            assetDoc.setCreator(newUser);
        }
    }

    /**
     * Set the rights objects based on the current right setting
     *
     * @throws XWikiException
     */
    public void applyRightsPolicy() throws XWikiException {
        applyRightsPolicy(null);
    }

    /**
     * Set the rights object based on the right in param or the current right setting if null
     *
     * @param right
     * @throws XWikiException
     */
    public void applyRightsPolicy(String right) throws XWikiException {
        XWikiDocument assetDoc = getDoc();
        assetDoc.removeObjects("XWiki.XWikiRights");

        BaseObject assetObj = assetDoc.getObject(Constants.ASSET_CLASS);
        String rights;

        if (right == null) {
            // Use existing rights value
            rights = assetObj.getStringValue(Constants.ASSET_CLASS_RIGHT);
            LOG.debug("applyRightsPolicy:  retrieved: '"+right+"'");
        } else {
            LOG.debug("applyRightsPolicy:  passed: '"+right+"'");
            rights = right;
            assetObj.setStringValue(Constants.ASSET_CLASS_RIGHT, right);
        }

        // Make sure rights value is valid, default to PUBLIC if not
        if (rights == null
            || !(rights.equals(Constants.ASSET_CLASS_RIGHT_PUBLIC)
                 || rights.equals(Constants.ASSET_CLASS_RIGHT_MEMBERS)
                 || rights.equals(Constants.ASSET_CLASS_RIGHT_PRIVATE))) {
            LOG.warn("Rights is being defaulted.  Got: '"+rights+"'");
            rights = Constants.ASSET_CLASS_RIGHT_PUBLIC;
            assetObj.setStringValue(Constants.ASSET_CLASS_RIGHT, rights);
        }

        // Rights
        BaseObject rightObj;

        // If collection is user
        String usergroupfield = Constants.RIGHTS_CLASS_USER;
        String uservalue =  ("".equals(assetDoc.getCreator())) ? context.getUser() : assetDoc.getCreator();
        String usergroupvalue = uservalue;

        // If collection is group
        if (assetDoc.getSpace().startsWith(Constants.GROUP_COLLECTION_SPACE_PREFIX)) {
            usergroupfield = Constants.RIGHTS_CLASS_GROUP;
            usergroupvalue = assetDoc.getSpace().substring(5) + ".MemberGroup";
        }

        // Always let the admin group have edit access
        rightObj = assetDoc.newObject(Constants.RIGHTS_CLASS, context);
        rightObj.setLargeStringValue(Constants.RIGHTS_CLASS_GROUP, Constants.RIGHTS_ADMIN_GROUP);
        rightObj.setStringValue("levels", "edit");
        rightObj.setIntValue("allow", 1);

        // Always let the creator/group edit
        rightObj = assetDoc.newObject(Constants.RIGHTS_CLASS, context);
        // CURRIKI-2468 - allow creator to always edit/view their creations
        if (usergroupfield.equals(Constants.RIGHTS_CLASS_GROUP)) {
            rightObj.setLargeStringValue(Constants.RIGHTS_CLASS_USER, uservalue);
        }
        rightObj.setLargeStringValue(usergroupfield, usergroupvalue);
        rightObj.setStringValue("levels", "edit");
        rightObj.setIntValue("allow", 1);

        if (rights.equals(Constants.ASSET_CLASS_RIGHT_PUBLIC)) {
            // Viewable by all and any member can edit
            rightObj = assetDoc.newObject(Constants.RIGHTS_CLASS, context);
            rightObj.setLargeStringValue(Constants.RIGHTS_CLASS_GROUP, Constants.RIGHTS_ALL_GROUP);
            rightObj.setStringValue("levels", "edit");
            rightObj.setIntValue("allow", 1);
        } else if (rights.equals(Constants.ASSET_CLASS_RIGHT_MEMBERS)) {
            // Viewable by all, only user/group can edit
        } else {
            // rights == private, so only allow creator/group to view and edit
            rightObj = assetDoc.newObject(Constants.RIGHTS_CLASS, context);
            // CURRIKI-2468 - allow creator to always edit/view their creations
            if (usergroupfield.equals(Constants.RIGHTS_CLASS_GROUP)) {
                rightObj.setLargeStringValue(Constants.RIGHTS_CLASS_USER, uservalue);
            }
            rightObj.setLargeStringValue(usergroupfield, usergroupvalue);
            rightObj.setStringValue("levels", "view");
            rightObj.setIntValue("allow", 1);

            // TODO: something here caused CURRIKI-3100 so the rest of the metadata could not get saved
            //private assets must be removed from review queue
            removeFromReviewQueue();
        }
    }

    public Class<? extends Asset> getAssetClass() {
        if (hasAccessLevel("view")) {
            if (isFolder()){
                if (isRootCollection()){
                    // Note that the Root Collection doesn't have an Asset Class
                    //  so it has to come before checking if no ASSET_CLASS
                    return RootCollectionCompositeAsset.class;
                } else if (isCollection()){
                    return CollectionCompositeAsset.class;
                } else {
                    return FolderCompositeAsset.class;
                }
            }

            String category = getCategory();
            if (category == null) {
                return InvalidAsset.class;
            }

            // call the sub type asset manager to get more details
            AssetManager assetManager = (AssetManager) DefaultAssetManager.getAssetSubTypeManager(category);
            if (assetManager!=null) {
                return assetManager.getAssetClass();
            } else {
                // Last is just an attachment item
                if (doc.getAttachmentList().size() > 0) {
                    return AttachmentAsset.class;
                }
                return Asset.class;
            }
        } else {
            return ProtectedAsset.class;
        }
    }

    public String getAssetType() {
        return getAssetClass().getSimpleName().replaceAll("Asset$", "");
    }

    public boolean isFolder() {
        if (doc.getObjectNumbers(Constants.COMPOSITE_ASSET_CLASS) == 0){
            return false;
        } else {
            // Work around a bug XWIKI-1624
            // TODO: Remove the work-around once XWIKI-1624 is fixed
            List<BaseObject> compObjs = doc.getObjects(Constants.COMPOSITE_ASSET_CLASS);
            for (BaseObject compObj : compObjs){
                if (compObj != null){
                    return true;
                }
            }
        }
        return false;
    }

    public boolean isCollection() {
        if (isFolder()) {
            com.xpn.xwiki.api.Object obj = getObject(Constants.COMPOSITE_ASSET_CLASS);
            if (getValue(Constants.COMPOSITE_ASSET_CLASS_TYPE, obj).equals(Constants.COMPOSITE_ASSET_CLASS_TYPE_COLLECTION)) {
                return true;
            }
        }
        return false;
    }

    public boolean isRootCollection() {
        if (isFolder()) {
            com.xpn.xwiki.api.Object obj = getObject(Constants.COMPOSITE_ASSET_CLASS);
            if (getValue(Constants.COMPOSITE_ASSET_CLASS_TYPE, obj).equals(Constants.COMPOSITE_ASSET_CLASS_TYPE_ROOT_COLLECTION)) {
                return true;
            }
        }
        return false;
    }


    static public Asset fetchAsset(String assetName, XWikiContext context) throws XWikiException {
        com.xpn.xwiki.api.XWiki xwikiApi = new com.xpn.xwiki.api.XWiki(context.getWiki(), context);
        Document doc = xwikiApi.getDocument(assetName);

        if (doc instanceof Asset){
            return ((Asset) doc).as(null);
        } else {
            throw new AssetException(AssetException.ERROR_ASSET_NOT_FOUND, "Asset "+assetName+" could not be found");
        }
    }

    static public Asset fetchAsset(String web, String page, XWikiContext context) throws XWikiException {
        return Asset.fetchAsset(web+"."+page, context);
    }

    public List<Property> getMetadata() {
        List<Property> md = new ArrayList<Property>();

        com.xpn.xwiki.api.Object assetObj = getObject(Constants.ASSET_CLASS);
        for (java.lang.Object prop : assetObj.getPropertyNames()) {
            LOG.debug("Adding "+prop+" to metadata list");
            md.add(assetObj.getProperty((String) prop));
        }

        com.xpn.xwiki.api.Object licenseObj = getObject(Constants.ASSET_LICENCE_CLASS);
        for (java.lang.Object prop : licenseObj.getPropertyNames()) {
            LOG.debug("Adding "+prop+" to metadata list");
            md.add(licenseObj.getProperty((String) prop));
        }

        // Add title now that it isn't in the asset object
        BaseStringProperty baseProp = new BaseStringProperty();
        baseProp.setName("title");
        baseProp.setValue(getTitle());
        Property prop = new Property(baseProp, context);
        md.add(prop);

        // Add creator
        baseProp = new BaseStringProperty();
        baseProp.setName("creator");
        baseProp.setValue(getCreator());
        prop = new Property(baseProp, context);
        md.add(prop);

        // Add creator's name
        String creatorName =  context.getWiki().getLocalUserName(context.getUser(), null, false, context);
        baseProp = new BaseStringProperty();
        baseProp.setName("creatorName");
        baseProp.setValue(creatorName);
        prop = new Property(baseProp, context);
        md.add(prop);

        // Add page's name
        baseProp = new BaseStringProperty();
        baseProp.setName("assetpage");
        baseProp.setValue(getFullName());
        prop = new Property(baseProp, context);
        md.add(prop);

        // Add rights List
        Map<String,Boolean> rightsList = getRightsList();
        String rightsListString = "{";
        for (String right : rightsList.keySet()) {
            if (rightsListString.length()>1) {
                rightsListString += ",";
            }
            rightsListString += "\""+right+"\":"+(rightsList.get(right)?"true":"false");
        }
        rightsListString += "}";

        baseProp = new BaseStringProperty();
        baseProp.setName("rightsList");
        baseProp.setValue(rightsListString);
        prop = new Property(baseProp, context);
        md.add(prop);

        // Add subcategory
        baseProp = new BaseStringProperty();
        baseProp.setName("subcategory");
        baseProp.setValue(getCategorySubtype());
        prop = new Property(baseProp, context);
        md.add(prop);

        // Add assetType to metadata
        Class assetType = getAssetClass();
        baseProp = new BaseStringProperty();
        baseProp.setName("fullAssetType");
        baseProp.setValue(assetType.getCanonicalName());
        prop = new Property(baseProp, context);
        md.add(prop);

        // And add shortAssetType to metadata
        String shortAssetType = assetType.getSimpleName();
        if (!shortAssetType.equals("Asset")) {
            shortAssetType = getAssetType();
        }
        baseProp = new BaseStringProperty();
        baseProp.setName("assetType");
        baseProp.setValue(shortAssetType);
        prop = new Property(baseProp, context);
        md.add(prop);

        baseProp = new BaseStringProperty();
        baseProp.setName("revision");
        baseProp.setValue(getVersion());
        prop = new Property(baseProp, context);
        md.add(prop);

        return md;
    }

    public <A extends Asset> A as(Class<? extends Asset> wantedClass) throws XWikiException {
        return (A) as(wantedClass, false);
    }

    public <A extends Asset> A subclassAs(Class<? extends Asset> wantedClass) throws XWikiException {
        return (A) this.as(wantedClass, true);
    }

    public <A extends Asset> A as(Class<? extends Asset> wantedClass, boolean moreSpecific) throws XWikiException {
        // If this is already a subtype of the wanted class then just return this
        if (wantedClass != null && wantedClass.isAssignableFrom(this.getClass())) {
            return (A) this;
        }

        Class returnClass;

        // Make sure the determined type is a subclass of the wanted type
        Class<? extends Asset> assetType = getAssetClass();
        if (!moreSpecific) {
            if (wantedClass == null) {
                // Return as whatever subtype it is
                returnClass = assetType;
            } else if (wantedClass.isAssignableFrom(assetType)) {
                // Return as the desired type
                returnClass = wantedClass;
            } else {
                throw new AssetException(AssetException.ERROR_ASSET_INCOMPATIBLE, "Document of type "+assetType+" cannot become "+wantedClass);
            }
        } else {
            // Subclass an Asset
            if (assetType.equals(wantedClass)) {
                // Already is that type, so don't initialize
                moreSpecific = false;
                returnClass = wantedClass;
            } else if (assetType.equals(Asset.class) && assetType.isAssignableFrom(wantedClass)) {
                // Turn into a specified subtype of Asset (initializing the subtype)
                returnClass = wantedClass;
            } else {
                throw new AssetException(AssetException.ERROR_ASSET_INCOMPATIBLE, "Document of type "+assetType+" cannot become "+wantedClass);
            }
        }

        Class[] parameterTypes = new Class[2];
        parameterTypes[0] = XWikiDocument.class;
        parameterTypes[1] = XWikiContext.class;

        java.lang.Object[] initargs = new java.lang.Object[2];
        initargs[0] = doc;
        initargs[1] = context;

        A subtyped;
        try {
            Constructor<A> constructor = returnClass.getConstructor(parameterTypes);
            subtyped = constructor.newInstance(initargs);
            if (moreSpecific) {
                subtyped.initSubType();
            }
        } catch (Exception e) {
            throw new AssetException(AssetException.ERROR_ASSET_INCOMPATIBLE, "Document of type "+assetType+" cannot become "+wantedClass, e);
        }

        return subtyped;
    }

    public void inheritMetadata() throws XWikiException {
        inheritMetadata(null, null);
    }

    public void inheritMetadata(String parentAsset) throws XWikiException {
        inheritMetadata(parentAsset, null);
    }

    public void inheritMetadata(String parentAsset, String publishSpace) throws XWikiException {
        assertCanEdit();
        XWikiDocument assetDoc = getDoc();

        BaseObject assetObj = assetDoc.getObject(Constants.ASSET_CLASS);
        if (assetObj == null) {
            assetObj = assetDoc.getObject(Constants.ASSET_CLASS, true, context);
        }

	boolean licenceSet = false;
        BaseObject newLicenceObj = doc.getObject(Constants.ASSET_LICENCE_CLASS);
        if (newLicenceObj == null) {
            newLicenceObj = doc.newObject(Constants.ASSET_LICENCE_CLASS, context);
        }

        // CURRIKI-2451 - Make sure group rights are used by default
        if (publishSpace != null && publishSpace.startsWith(Constants.GROUP_COLLECTION_SPACE_PREFIX)) {
            String groupSpace = publishSpace.replaceFirst("^"+Constants.GROUP_COLLECTION_SPACE_PREFIX, Constants.GROUP_SPACE_PREFIX);
            String rights = Constants.ASSET_CLASS_RIGHT_PUBLIC;

            // TODO: This should probably be using the SpaceManager extension
            XWikiDocument groupSpaceDoc = context.getWiki().getDocument(groupSpace+"."+Constants.GROUP_RIGHTS_PAGE, context);
            if (groupSpaceDoc != null){
                // Note that the values for the group access defaults
                //  DO NOT MATCH the values that need to be applied to the collection
                BaseObject rObj = groupSpaceDoc.getObject(Constants.GROUP_RIGHTS_CLASS);
                if (rObj != null){
                    String groupDefaultPrivs = rObj.getStringValue(Constants.GROUP_RIGHTS_PROPERTY);
                    if (groupDefaultPrivs.equals(Constants.GROUP_RIGHT_PRIVATE)){
                        rights = Constants.ASSET_CLASS_RIGHT_PRIVATE;
                    } else if (groupDefaultPrivs.equals(Constants.GROUP_RIGHT_PROTECTED)){
                        rights = Constants.ASSET_CLASS_RIGHT_MEMBERS;
                    } else if (groupDefaultPrivs.equals(Constants.GROUP_RIGHT_PUBLIC)){
                        rights = Constants.ASSET_CLASS_RIGHT_PUBLIC;
                    }


                    // CURRIKI-4377 - inherit grade level, topic, language, and licence from group
                    if (parentAsset == null || parentAsset.length() == 0) {
                        // educationLevel
                        List edLevels = rObj.getListValue(Constants.GROUP_DEFAULT_GRADE_PROPERTY);
                        assetObj.setDBStringListValue(Constants.ASSET_CLASS_EDUCATIONAL_LEVEL, edLevels);

                        // topic
                        List topics = rObj.getListValue(Constants.GROUP_DEFAULT_TOPIC_PROPERTY);
                        assetObj.setDBStringListValue(Constants.ASSET_CLASS_FRAMEWORK_ITEMS, topics);

                        // language
                        String lang = rObj.getStringValue(Constants.GROUP_DEFAULT_LANGUAGE_PROPERTY);
                        assetObj.setStringValue(Constants.ASSET_CLASS_LANGUAGE, lang);

                        // licence
                        String licence = rObj.getStringValue(Constants.GROUP_DEFAULT_LICENCE_PROPERTY);
                        newLicenceObj.setStringValue(Constants.ASSET_LICENCE_ITEM_LICENCE_TYPE, licence);
			licenceSet = true;
                    }
                }
            }

            assetObj.setStringValue(Constants.ASSET_CLASS_RIGHT, rights);
        }

        XWikiDocument parentDoc = null;
        if (parentAsset != null && parentAsset.length() > 0) {
            parentDoc = context.getWiki().getDocument(parentAsset, context);
            if (parentDoc.isNew()) {
                throw new AssetException(AssetException.MODULE_PLUGIN_ASSET, AssetException.ERROR_ASSET_NOT_FOUND, "Parent asset not found");
            }
        }
        // the Root collection does not have a proper asset class so we can't inherit from it
        if (parentDoc != null && parentDoc.getObject(Constants.ASSET_CLASS) != null && !parentDoc.getName().equals(Constants.ROOT_COLLECTION_PAGE)) {
            BaseObject parentAssetObj = (BaseObject) parentDoc.getObject(Constants.ASSET_CLASS).clone();

            copyProperty(parentAssetObj, assetObj, Constants.ASSET_CLASS_EDUCATIONAL_LEVEL);
            copyProperty(parentAssetObj, assetObj, Constants.ASSET_CLASS_FRAMEWORK_ITEMS);
            copyProperty(parentAssetObj, assetObj, Constants.ASSET_CLASS_RIGHT);
            copyProperty(parentAssetObj, assetObj, Constants.ASSET_CLASS_KEYWORDS);
            copyProperty(parentAssetObj, assetObj, Constants.ASSET_CLASS_LANGUAGE);
        }

        // make sure default rights value is not empty (default to public)
        String rights = assetObj.getStringValue(Constants.ASSET_CLASS_RIGHT);
        if ((rights==null)||(rights.equals(""))) {
            assetObj.setStringValue(Constants.ASSET_CLASS_RIGHT, Constants.ASSET_CLASS_RIGHT_PUBLIC);
        }

        // the Root collection does not have an asset Licence class
        if (parentDoc != null && parentDoc.getObject(Constants.ASSET_LICENCE_CLASS) != null) {
            BaseObject parentLicenceObjAsset = parentDoc.getObject(Constants.ASSET_LICENCE_CLASS);
            copyProperty(parentLicenceObjAsset, newLicenceObj, Constants.ASSET_LICENCE_ITEM_LICENCE_TYPE);
        } else if (!licenceSet) {
            newLicenceObj.setStringValue(Constants.ASSET_LICENCE_ITEM_LICENCE_TYPE, Constants.ASSET_LICENCE_ITEM_LICENCE_TYPE_DEFAULT);
        }

        // Rights holder should be by default the pretty name of the user
        newLicenceObj.setStringValue(Constants.ASSET_LICENCE_ITEM_RIGHTS_HOLDER, context.getWiki().getLocalUserName(context.getUser(), null, false, context));
    }

    /**
     * Make a folder in the current asset
     * @return
     * @throws XWikiException
     */
    public FolderCompositeAsset makeFolder() throws XWikiException {
        return makeFolder(null);
    }

    /**
     * Make a folder in the current asset
     * @param page
     * @return
     * @throws XWikiException
     */
    public FolderCompositeAsset makeFolder(String page) throws XWikiException {
        assertCanEdit();
        FolderCompositeAsset asset = subclassAs(FolderCompositeAsset.class);

        if (page != null) {
            asset.addSubasset(page);
        }
        saveDocument(context.getMessageTool().get("curriki.comment.createfoldersourceasset"), true);
        return asset;
    }

    /**
     * Make a collection in the current asset
     * @return
     * @throws XWikiException
     */
    public CollectionCompositeAsset makeCollection() throws XWikiException {
        return makeCollection(null);
    }

    /**
     * Make a composite collection in the current asset
     * @param page
     * @return
     * @throws XWikiException
     */
    public CollectionCompositeAsset makeCollection(String page) throws XWikiException {
        assertCanEdit();
        CollectionCompositeAsset asset = subclassAs(CollectionCompositeAsset.class);

        if (page != null) {
            asset.addSubasset(page);
        }
        saveDocument(context.getMessageTool().get("curriki.comment.createfoldersourceasset"), true);
        return asset;
    }

    /**
     * Make an attachment asset based on the type of attachment
     * This will call the appropriate subtype asset manager for further processing
     * @return an AttachmentAsset object
     * @throws XWikiException
     */
    public AttachmentAsset processAttachment() throws XWikiException {
        assertCanEdit();
        AttachmentAsset asset = subclassAs(AttachmentAsset.class);

        if (doc.getAttachmentList().size() > 0) {
            XWikiAttachment attach = doc.getAttachmentList().get(0);
            determineFileTypeAndCategory(attach);
            saveDocument(context.getMessageTool().get("curriki.comment.createtextsourceasset"), true);
        }

        return asset;
    }

    /**
     * This functions determines the file type and the category based on an attachment
     * @param attachment
     * @throws XWikiException
     */
    protected void determineFileTypeAndCategory(XWikiAttachment attachment) throws XWikiException {
        String filename = attachment.getFilename();
        String extension = (filename.lastIndexOf(".") != -1 ? filename.substring(filename.lastIndexOf(".") + 1).toLowerCase(): null);
        MimeTypePlugin mimePlugin = getMimeTypePlugin();
        String filetype =  mimePlugin.getFileType(extension, context);
        String category = mimePlugin.getCategory(filetype, context);
        XWikiDocument assetDoc = getDoc();

        // set the attachment information
        BaseObject documentObject = assetDoc.getObject(Constants.ATTACHMENT_ASSET_CLASS, true, context);
        documentObject.setStringValue(Constants.ATTACHMENT_ASSET_FILE_TYPE, filetype);
        documentObject.setLongValue(Constants.ATTACHMENT_ASSET_FILE_SIZE, attachment.getFilesize());

        // set the category
        BaseObject assetObj = assetDoc.getObject(Constants.ASSET_CLASS, true, context);
        assetObj.setStringValue(Constants.ASSET_CLASS_CATEGORY, category);

        // call the sub type asset manager to get more details
        AssetManager assetManager = DefaultAssetManager.getAssetSubTypeManager(category);
        if (assetManager!=null) {
            assetManager.updateSubAssetClass(assetDoc, filetype, category, attachment, context);
        }
    }

    protected MimeTypePlugin getMimeTypePlugin() {
        MimeTypePlugin mimePlugin = (MimeTypePlugin) context.getWiki().getPlugin(MimeTypePlugin.PLUGIN_NAME, context);
        return mimePlugin;
    }

    public Boolean isPublished() {
        return !getSpace().equals("AssetTemp");
    }

    public Asset publish(String space) throws XWikiException {
        return publish(space, true);
    }

    public Asset publish(String space, String name) throws XWikiException {
        return publish(space, name, true);
    }

    public Asset publish(String space, boolean checkSpace) throws XWikiException {
        String prettyName = context.getWiki().clearName(getTitle(), true, true, context);

        return publish(space, prettyName, checkSpace);
    }

    public Asset publish(String space, String name, boolean checkSpace) throws XWikiException {
        if (isPublished()) {
            throw new AssetException("This resource is already published");
        }

        assertCanEdit();

        XWikiDocument assetDoc = getDoc();
        // Be sure we have a category first -- attachments can't get them automatically yet
        BaseObject obj = assetDoc.getObject(Constants.ASSET_CLASS);
        if (obj != null) {
            String category = obj.getStringValue(Constants.ASSET_CLASS_CATEGORY);
            if (category == null || category.length() == 0 || category.equals(Constants.ASSET_CATEGORY_UNKNOWN)) {
                processAttachment();
            }
        }

        if (!space.startsWith(Constants.COLLECTION_PREFIX)) {
            throw new AssetException("You cannot publish to the space "+space);
        }

        if (!validate()) {
            throw new AssetException("Validation failed.");
        }

        if (checkSpace) {
            CollectionSpace.ensureExists(space, context);
        }

        // Let's choose a nice name for the page
        String prettyName = context.getWiki().clearName(name, true, true, context);
        //rename(space + "." + context.getWiki().getUniquePageName(space, prettyName.trim(), context), new ArrayList<String>());
        this.doc = assetDoc.copyDocument(space + "." + context.getWiki().getUniquePageName(space, prettyName.trim(), context),context);
        context.getWiki().deleteDocument(assetDoc, context);

        applyRightsPolicy();

        List<String> params = new ArrayList<String>();
        params.add(assetDoc.getStringValue(Constants.ASSET_CLASS_CATEGORY));
        save(context.getMessageTool().get("curriki.comment.finishcreatingsubasset", params));

        if (isCollection()) {
            RootCollectionCompositeAsset root = CollectionSpace.getRootCollection(space, context);
            root.addSubasset(this.getFullName());
            root.saveDocument(context.getMessageTool().get("curriki.comment.addtocollectionasset"));
        }

        return this;
    }

    public boolean validate() throws XWikiException {
        // Has the asset been subtyped ?
        if (getAssetClass().equals(Asset.class)) {
            throw new AssetException("This asset is not complete.");
        }

        // Other validation

        // Super's validate
        return super.validate();
{'fraction_non_alphanumeric': 0.0943764582638922, 'fraction_numerical': 0.0016070663301747535, 'mean_word_length': 2.5269544042320935, 'pattern_counts': {'":': 4, '<': 39, '<?xml version=': 0, '>': 49, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '17264', 'n_tokens_mistral': 22807, 'n_tokens_neox': 22064, 'n_words': 5307}