Index: kernel-impl/src/main/java/org/sakaiproject/content/impl/DbContentService.java =================================================================== --- kernel-impl/src/main/java/org/sakaiproject/content/impl/DbContentService.java (revision 107516) +++ kernel-impl/src/main/java/org/sakaiproject/content/impl/DbContentService.java (working copy) @@ -1727,57 +1727,74 @@ { BaseResourceEdit redit = (BaseResourceEdit) edit; boolean ok = true; - if (redit.m_body == null) - { - if (redit.m_contentStream == null) - { - // no body and no stream -- may result from edit in which body is not accessed or modified - M_log.debug("ContentResource committed with no change to contents (i.e. no body and no stream for content): " - + edit.getReference()); + + String referenceResourceId = redit.referenceCopy; + if (referenceResourceId != null) { + // special handling for reference commits + if (M_log.isDebugEnabled()) M_log.debug("Making resource ("+redit.getId()+") reference copy of DB resource ("+referenceResourceId+"), body/contentStream is ignored"); + if (m_bodyPath == null) { + // for reference we just move the binary data location to point at the new one + String sql = "update "+m_resourceBodyTableName+" set RESOURCE_ID=? where RESOURCE_ID=?"; + // this write could fail if we try to move it to a taken resource_id, no way to recover if it does + ok = m_sqlService.dbWrite(sql, new Object[] {referenceResourceId, redit.getId()}); + if (M_log.isDebugEnabled()) M_log.debug("Moving RESOURCE_ID ("+redit.getId()+") to ("+referenceResourceId+") for DB stored content data ("+m_resourceBodyTableName+"), success="+ok); } - else + } else { + // normal handling (write the resource content data) + if (M_log.isDebugEnabled()) M_log.debug("Normal resource ("+redit.getId()+") body/contentStream storage"); + if (redit.m_body == null) { - message += "from stream "; - // if we have been configured to use an external file system - if (m_bodyPath != null) + if (redit.m_contentStream == null) { - message += "to file"; - ok = putResourceBodyFilesystem(edit, redit.m_contentStream); + // no body and no stream -- may result from edit in which body is not accessed or modified + M_log.debug("ContentResource committed with no change to contents (i.e. no body and no stream for content): " + + edit.getReference()); } - - // otherwise use the database else { - message += "to database"; - ok = putResourceBodyDb(edit, redit.m_contentStream); + message += "from stream "; + // if we have been configured to use an external file system + if (m_bodyPath != null) + { + message += "to file"; + ok = putResourceBodyFilesystem(edit, redit.m_contentStream); + } + + // otherwise use the database + else + { + message += "to database"; + ok = putResourceBodyDb(edit, redit.m_contentStream); + } } } - } - else - { - message += "from byte-array "; - byte[] body = ((BaseResourceEdit) edit).m_body; - ((BaseResourceEdit) edit).m_body = null; + else + { + message += "from byte-array "; + byte[] body = ((BaseResourceEdit) edit).m_body; + ((BaseResourceEdit) edit).m_body = null; - // update the resource body - if (body != null) - { - // if we have been configured to use an external file - // system - if (m_bodyPath != null) + // update the resource body + if (body != null) { - message += "to file"; - ok = putResourceBodyFilesystem(edit, body); - } + // if we have been configured to use an external file + // system + if (m_bodyPath != null) + { + message += "to file"; + ok = putResourceBodyFilesystem(edit, body); + } - // otherwise use the database - else - { - message += "to database"; - ok = putResourceBodyDb(edit, body); + // otherwise use the database + else + { + message += "to database"; + ok = putResourceBodyDb(edit, body); + } } } } + if (!ok) { cancelResource(edit); @@ -1852,6 +1869,11 @@ public void removeResource(ContentResourceEdit edit) { + removeResource(edit, true); + } + + public void removeResource(ContentResourceEdit edit, boolean removeContent) + { // delete the body boolean goin = in(); try @@ -1863,20 +1885,28 @@ else { - // if we have been configured to use an external file system if (m_bodyPath != null) { - delResourceBodyFilesystem(edit); + // if we have been configured to use an external file system + if (removeContent) { + M_log.info("Removing resource ("+edit.getId()+") content: "+m_bodyPath); + delResourceBodyFilesystem(edit); + } else { + M_log.info("Removing original resource reference ("+edit.getId()+") without removing the actual content: "+m_bodyPath); + } } - - // otherwise use the database else { - delResourceBodyDb(edit); + // otherwise use the database + if (removeContent) { + delResourceBodyDb(edit); + M_log.info("Removing resource ("+edit.getId()+") DB content"); + } else { + M_log.info("Removing original resource reference ("+edit.getId()+") without removing the actual DB content"); + } } // clear the memory image of the body - byte[] body = ((BaseResourceEdit) edit).m_body; ((BaseResourceEdit) edit).m_body = null; if(isInsideIndividualDropbox(edit.getId())) @@ -2152,9 +2182,9 @@ */ protected boolean putResourceBodyDb(ContentResourceEdit resource, byte[] body) { - if ((body == null) || (body.length == 0)) return true; + if (M_log.isDebugEnabled()) M_log.debug("Making resource ("+resource.getId()+") copy of DB resource body"); // delete the old String statement = contentServiceSql.getDeleteContentSql(m_resourceBodyTableName); @@ -2166,7 +2196,9 @@ // add the new statement = contentServiceSql.getInsertContentSql(m_resourceBodyTableName); - return m_sqlService.dbWriteBinary(statement, fields, body, 0, body.length); + boolean success = m_sqlService.dbWriteBinary(statement, fields, body, 0, body.length); + if (M_log.isDebugEnabled()) M_log.debug("putResourceBodyDb: resource ("+resource.getId()+") put success="+success); + return success; /* * %%% BLOB code // read the record's blob and update statement = "select body from " + m_resourceTableName + " where ( resource_id = '" + Index: kernel-impl/src/main/java/org/sakaiproject/content/impl/BaseContentService.java =================================================================== --- kernel-impl/src/main/java/org/sakaiproject/content/impl/BaseContentService.java (revision 107516) +++ kernel-impl/src/main/java/org/sakaiproject/content/impl/BaseContentService.java (working copy) @@ -823,8 +823,6 @@ M_log.warn("init(): ", t); } - - this.m_useSmartSort = m_serverConfigurationService.getBoolean("content.smartSort", true); } // init @@ -4346,8 +4344,19 @@ */ public boolean allowRename(String id, String new_id) { - M_log.warn("allowRename(" + id + ") - Rename not implemented"); - return false; + // check security for remove resource (own or any) + boolean allowed = false; + if ( allowRemove(id) ) { + // check security for read resource + if ( unlockCheck(AUTH_RESOURCE_READ, id) ) { + // check security for add resource + if ( unlockCheck(AUTH_RESOURCE_ADD, new_id) ) { + allowed = true; + } + } + } + if (M_log.isDebugEnabled()) M_log.debug("content allowRename("+id+", "+new_id+") = "+allowed); + return allowed; // return unlockCheck(AUTH_RESOURCE_ADD, new_id) && // unlockCheck(AUTH_RESOURCE_REMOVE, id); @@ -4397,7 +4406,7 @@ ContentResourceEdit thisResource = null; ContentCollectionEdit thisCollection = null; - if (M_log.isDebugEnabled()) M_log.debug("copy(" + id + "," + new_id + ")"); + if (M_log.isDebugEnabled()) M_log.debug("rename(" + id + "," + new_id + ")"); if (m_storage.checkCollection(id)) { @@ -4420,21 +4429,21 @@ throw new IdUnusedException(id); } - if (isCollection) - { - new_id = copyCollection(thisCollection, new_id); - removeCollection(thisCollection); - } - else - { - new_id = copyResource(thisResource, new_id); - removeResource(thisResource); - } + // makes a copy each time content is renamed + if (isCollection) { + // NOTE: this does NOT do a deep copy (i.e. the collection is copied but the content is not) + new_id = copyCollection(thisCollection, new_id); + removeCollection(thisCollection); + } else { + // rename needs to set referenceCopy + new_id = copyResource(thisResource, new_id, true); + removeResource(thisResource); + } return new_id; } // rename - /** + /** * check permissions for copy(). * * @param id @@ -4833,7 +4842,14 @@ { ContentResourceEdit edit = addResource(new_id); edit.setContentType(thisResource.getContentType()); - edit.setContent(thisResource.streamContent()); + // NOTE: we always do a reference copy on a move if we can + boolean referenceCopy = false; + if (edit instanceof BaseResourceEdit) { + ((BaseResourceEdit)edit).setReferenceCopy(thisResource.getId()); + referenceCopy = true; + } else { + edit.setContent(thisResource.streamContent()); + } edit.setResourceType(thisResource.getResourceType()); edit.setAvailability(thisResource.isHidden(), thisResource.getReleaseDate(), thisResource.getRetractDate()); @@ -4884,7 +4900,8 @@ // TODO - we don't know whether to post a future notification or not postAvailableEvent(edit, ref, NotificationService.NOTI_NONE); - m_storage.removeResource(thisResource); + // we need to not remove the content if we just did a reference copy above (or remove the content when there was no reference copy) + m_storage.removeResource(thisResource, !referenceCopy); // track it (no notification) String thisRef = thisResource.getReference(null); @@ -5020,10 +5037,8 @@ /** * Copy a resource. * - * @param thisResource - * The resource to be copied - * @param new_id - * The desired id of the new resource. + * @param resource The resource to be copied + * @param new_id The desired id of the new resource. * @return The full id of the new copy of the resource. * @exception PermissionException * if the user does not have permissions to read a containing collection, or to remove this resource. @@ -5043,9 +5058,38 @@ public String copyResource(ContentResource resource, String new_id) throws PermissionException, IdUnusedException, TypeException, InUseException, OverQuotaException, IdUsedException, ServerOverloadException { + return copyResource(resource, new_id, false); + } + + /** + * Copy a resource with an option to do a reference copy + * + * @param resource + * @param new_id + * @param referenceCopy if true, then do not copy the actual content (only make a reference copy which points to it), + * if false, then copy like normal (duplicate the content) + * @return The full id of the new copy of the resource. + * @exception PermissionException + * if the user does not have permissions to read a containing collection, or to remove this resource. + * @exception IdUnusedException + * if the resource id is not found. + * @exception TypeException + * if the resource is a collection. + * @exception InUseException + * if the resource is locked by someone else. + * @exception OverQuotaException + * if copying the resource would exceed the quota. + * @exception IdUsedException + * if a unique id cannot be found in some arbitrary number of attempts (@see MAXIMUM_ATTEMPTS_FOR_UNIQUENESS). + * @exception ServerOverloadException + * if the server is configured to write the resource body to the filesystem and the save fails. + */ + protected String copyResource(ContentResource resource, String new_id, boolean referenceCopy) throws PermissionException, IdUnusedException, + TypeException, InUseException, OverQuotaException, IdUsedException, ServerOverloadException + { if (M_log.isDebugEnabled()) { - M_log.debug("copyResource: " + resource.getId() + " to " + new_id); + M_log.debug("copyResource: " + resource.getId() + " to " + new_id + ", reference="+referenceCopy); } if (StringUtils.isBlank(new_id)) { @@ -5084,11 +5128,19 @@ try { edit = addResource(new_id); + // this duplicates a lot of the code from BaseResourceEdit.set() edit.setContentType(resource.getContentType()); - // use stream instead of byte array - // edit.setContent(resource.getContent()); - edit.setContent(resource.streamContent()); + if (referenceCopy && edit instanceof BaseResourceEdit) { + // do a reference copy so the actual content is not duplicated + ((BaseResourceEdit)edit).setReferenceCopy(resource.getId()); + if (M_log.isDebugEnabled()) M_log.debug("copyResource doing a reference copy of "+resource.getId()); + } else { + // use stream instead of byte array + // edit.setContent(resource.getContent()); + edit.setContent(resource.streamContent()); + if (M_log.isDebugEnabled()) M_log.debug("copyResource doing a normal copy"); + } edit.setResourceType(resource.getResourceType()); ResourcePropertiesEdit newProps = edit.getPropertiesEdit(); @@ -5154,7 +5206,7 @@ * The desired id of the new collection. * @return The full id of the new copy of the resource. * @exception PermissionException - * if the user does not have permissions to perform the operations + * if the user does not have permissions to perform the operations OR the collection has members in it * @exception IdUnusedException * if the collection id is not found. * @exception TypeException @@ -5211,7 +5263,9 @@ } // copyCollection /** - * Make a deep copy of a collection. Creates a new collection with an id similar to new_folder_id and recursively copies all nested collections and resources within thisCollection to the new collection. + * Make a deep copy of a collection. + * Creates a new collection with an id similar to new_folder_id and recursively copies all nested collections and resources within thisCollection to the new collection. + * Only used in "copyIntoFolder" for now * * @param thisCollection * The collection to be copied @@ -11254,6 +11308,24 @@ protected String m_oldDisplayName = null; /** + * Indicates this resource is a reference copy of an existing resource, + * this id will be the resource it is a copy of + */ + protected String referenceCopy = null; + /** + * Indicates this resource is a reference copy of an existing resource, + * this id will be the resource it is a copy of + * WARNING: this will null out the content values (stream and body) + * + * @param referenceCopy the id of the resource this is a copy of + */ + public void setReferenceCopy(String referenceCopy) { + this.referenceCopy = referenceCopy; + this.m_contentStream = null; + this.m_body = null; + } + + /** * Construct. * * @param id @@ -11287,6 +11359,10 @@ set(other); } // BaseResourceEdit + public BaseResourceEdit(ContentResource other, boolean reference) { + set(other, reference); + } // BaseResourceEdit + /** * Set the file path for this resource * @@ -11309,13 +11385,22 @@ } /** - * Take all values from this object. + * Take all values from this object * - * @param user + * @param other * The other object to take values from. */ - protected void set(ContentResource other) - { + protected void set(ContentResource other) { + set(other, false); + } + + /** + * Set the values in this edit to equal the values in the passing object + * + * @param other the object to take values from. + * @param reference if true then make a reference copy (i.e. do not duplicate the actual + */ + protected void set(ContentResource other, boolean reference) { m_id = other.getId(); m_contentType = other.getContentType(); m_contentLength = other.getContentLength(); @@ -11323,20 +11408,29 @@ chh = other.getContentHandler(); chh_vce = other.getVirtualContentEntity(); - this.m_contentStream = ((BaseResourceEdit) other).m_contentStream; + if (reference && other.getId() != null) { + // populate the reference copy key, skip populating the actual content + this.referenceCopy = other.getId(); + this.m_contentStream = null; + this.m_body = null; + } else { + // copy the actual content + this.referenceCopy = null; + this.m_contentStream = ((BaseResourceEdit) other).m_contentStream; + // if there's a body in the other, reference it, else leave this one null + // Note: this treats the body byte array as immutable, so to update it one + // *must* call setContent() not just getContent and mess with the bytes. -ggolden + byte[] content = ((BaseResourceEdit) other).m_body; + if (content != null) + { + m_contentLength = content.length; + m_body = content; + } + } + m_filePath = ((BaseResourceEdit) other).m_filePath; - // if there's a body in the other, reference it, else leave this one null - // Note: this treats the body byte array as immutable, so to update it one - // *must* call setContent() not just getContent and mess with the bytes. -ggolden - byte[] content = ((BaseResourceEdit) other).m_body; - if (content != null) - { - m_contentLength = content.length; - m_body = content; - } - // copy other's access mode and list of groups m_access = other.getAccess(); m_groups.clear(); @@ -12545,11 +12639,21 @@ public void cancelResource(ContentResourceEdit edit); /** - * Forget about a resource. + * Forget about a resource (and associated content). + * + * @param resource the resource to remove */ public void removeResource(ContentResourceEdit resource); /** + * Forget about a resource with the option to leave the content in place + * + * @param resource the resource to remove + * @param removeContent if true, then also remove the content + */ + public void removeResource(ContentResourceEdit resource, boolean removeContent); + + /** * Read the resource's body. * * @exception ServerOverloadException