libreccm-legacy/ccm-docmgr/src-2/com/arsdigita/docmgr/ResourceImpl.java

700 lines
21 KiB
Java
Executable File

/*
* Copyright (C) 2001, 2002 Red Hat Inc. All Rights Reserved.
*
* The contents of this file are subject to the CCM Public
* License (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of
* the License at http://www.redhat.com/licenses/ccmpl.html
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
*/
package com.arsdigita.docmgr;
import java.math.BigDecimal;
import java.net.URL;
import java.sql.SQLException;
import java.util.StringTokenizer;
import java.util.Vector;
import javax.servlet.http.HttpServletRequest;
import org.apache.oro.text.perl.Perl5Util;
import com.arsdigita.db.Sequences;
import com.arsdigita.domain.DataObjectNotFoundException;
import com.arsdigita.domain.DomainObjectFactory;
import com.arsdigita.kernel.ACSObject;
import com.arsdigita.kernel.Kernel;
import com.arsdigita.kernel.KernelExcursion;
import com.arsdigita.kernel.Party;
import com.arsdigita.kernel.User;
import com.arsdigita.kernel.permissions.PermissionDescriptor;
import com.arsdigita.kernel.permissions.PermissionService;
import com.arsdigita.kernel.permissions.PrivilegeDescriptor;
import com.arsdigita.persistence.DataCollection;
import com.arsdigita.persistence.DataObject;
import com.arsdigita.persistence.DataOperation;
import com.arsdigita.persistence.DataQuery;
import com.arsdigita.persistence.Filter;
import com.arsdigita.persistence.OID;
import com.arsdigita.persistence.PersistenceException;
import com.arsdigita.persistence.Session;
import com.arsdigita.persistence.SessionManager;
import com.arsdigita.persistence.metadata.ObjectType;
import com.arsdigita.versioning.VersionedACSObject;
import com.arsdigita.web.Web;
/**
* This class is the abstract parent class of {@link File} and {@link
* Folder}. It provides an implementation of the {@link Resource}
* interface for a database-backed virtual filesystem.
*
* This implementation stores file in Oracle exclusively.
* All versioning API is by inheritance from VersionedACSObject
* (which will eventually
* also include access logging - so wait for VersionedACSObject)
*
* @author Stefan Deusch (stefan@arsdigita.com)
* @author Ron Henderson (ron@arsdigita.com)
* @version $Revision: #13 $ $Date: 2003/07/11 $ $Author: jparsons $
*/
public abstract class ResourceImpl extends VersionedACSObject
implements Resource, Constants {
public static final String BASE_DATA_OBJECT_TYPE =
"com.arsdigita.docs.ResourceImpl";
public static final String PARENT = "parent";
protected static org.apache.log4j.Logger s_log =
org.apache.log4j.Logger.getLogger(ResourceImpl.class);
protected String getBaseDataObjectType() {
return BASE_DATA_OBJECT_TYPE;
}
/**
* Default constructor. The contained <code>DataObject</code> is
* initialized with a new <code>DataObject</code> with an
* <code>ObjectType</code> of BASE_DATA_OBJECT_TYPE.
*
* @see com.arsdigita.domain.DomainObject#DomainObject(String)
* @see com.arsdigita.persistence.metadata.ObjectType
*/
protected ResourceImpl() {
this(BASE_DATA_OBJECT_TYPE);
}
/**
* Constructor. The contained <code>DataObject</code> is
* initialized with a new <code>DataObject</code> with an
* <code>ObjectType</code> specified by the string
* <i>typeName</i>.
*
* @param typeName The name of the <code>ObjectType</code> of the
* contained <code>DataObject</code>.
*
* @see com.arsdigita.domain.DomainObject#DomainObject(String)
* @see com.arsdigita.persistence.DataObject
* @see com.arsdigita.persistence.metadata.ObjectType
*/
protected ResourceImpl(String typeName) {
super(typeName);
}
/**
* Constructor. The contained <code>DataObject</code> is
* initialized with a new <code>DataObject</code> with an
* <code>ObjectType</code> specified by <i>type</i>.
*
* @param type The <code>ObjectType</code> of the contained
* <code>DataObject</code>.
*
* @see com.arsdigita.domain.DomainObject#DomainObject(ObjectType)
* @see com.arsdigita.persistence.DataObject
* @see com.arsdigita.persistence.metadata.ObjectType
*/
protected ResourceImpl(ObjectType type) {
super(type);
}
/**
* Creates a new resource. Properties of this object are not made
* persistent until the <code>save()</code> method is called.
*/
protected ResourceImpl(DataObject dataObject) {
super(dataObject);
}
/**
* Creates a resource given the ID
*
* @param id the BigDecimal ID
*/
protected ResourceImpl(BigDecimal id) throws DataObjectNotFoundException {
this(new OID(BASE_DATA_OBJECT_TYPE, id));
}
/**
* Creates a resource given the OID
*
* @param oid the Object OID
*
*/
protected ResourceImpl(OID oid) throws DataObjectNotFoundException {
super(oid);
}
/**
* Creates a named resource of the specified type.
*/
protected ResourceImpl(String type,
String name,
String description) {
this(type);
setName(name);
setDescription(description);
}
/**
* Creates a named resource of the specified type with a given parent.
*/
protected ResourceImpl(String type,
String name,
String description,
ResourceImpl parent) {
this(type,name,description);
setParent(parent);
}
/**
* Creates an empty resource with the given parent.
*/
protected ResourceImpl(String type,
ResourceImpl parent) {
this(type);
setParent(parent);
}
private boolean m_wasNew;
protected void beforeSave() {
m_wasNew = isNew();
s_log.debug("Before save on " + getID(), new Throwable());
/**
* Update the path for this resource before saving. It should be
* sufficient to update the path only when the name changes, but
* the current implementation is conservative about path updates.
*/
final boolean pathChanged =
isPropertyModified(PARENT) || isPropertyModified(NAME);
if (pathChanged) {
String oldPath = null;
if (!isNew()) {
oldPath = getPath();
}
final Resource parent = getParent();
if (parent != null) {
final String parentPath = parent.getPath();
setPath(parentPath + SEPARATOR + getName());
} else {
setPath(SEPARATOR + getName());
}
if (oldPath != null) {
updateChildren(oldPath);
}
}
if(isNew()) {
User user = Web.getContext().getUser();
setCreationUser(user);
setLastModifiedUser(user);
java.util.Date date = new java.util.Date();
setCreationDate(date);
setLastModifiedDate(date);
setCreationIP();
} else {
User user = Web.getContext().getUser();
setLastModifiedUser(user);
setLastModifiedDate(new java.util.Date());
setLastModifiedIP();
}
super.beforeSave();
}
protected void afterSave() {
super.afterSave();
if (m_wasNew && !isRoot()) {
Object obj = getParent();
if (obj instanceof ACSObject) {
PermissionService.setContext(this, (ACSObject) obj);
}
new KernelExcursion() {
protected void excurse() {
Party currentParty = Kernel.getContext().getParty();
setParty(Kernel.getSystemParty());
PermissionService.grantPermission
(new PermissionDescriptor(PrivilegeDescriptor.ADMIN,
ResourceImpl.this,
currentParty));
}
}.run();
}
}
/**
* Deletes a resource. Throws a {@link ResourceNotEmptyException}
* if the resource still contains children.
*/
public void delete() throws ResourceNotEmptyException {
// We mangle names here so that a deleted "foo" doesn't stop
// the user from adding a new "foo".
//
// XXX: This is not a pleasing way to fix this, but the truly
// right thing to do is fix versioning, not doc repos's use of
// versioning.
String name = getName();
String suffix = "";
try {
suffix = "-" + Sequences.getNextValue();
} catch (SQLException se) {
throw new PersistenceException(se.getMessage());
}
int suffixLength = suffix.length();
int nameLength = name.length();
if (nameLength > suffixLength) {
setName(name.substring(0, nameLength - suffixLength) + suffix);
} else {
setName(name + suffix);
}
save();
try {
super.delete();
} catch (PersistenceException ex) {
throw new ResourceNotEmptyException(ex.getMessage());
}
}
/**
* Update the denormalized path for each child of this resource.
*/
private void updateChildren(String oldPath) {
String path = getPath();
// Execute the data operation to update all children
Session session = SessionManager.getSession();
DataOperation op = session.retrieveDataOperation
("com.arsdigita.docs.updateChildren");
op.setParameter("rootPath", path);
op.setParameter("oldPath", oldPath);
op.setParameter("oldRootPathLength", new Integer(oldPath.length()+1));
op.setParameter("parentResource", getID());
op.execute();
op.close();
}
public String getName() {
return (String) get(NAME);
}
public void setName(String name) {
set(NAME, name);
}
public String getDescription() {
return (String) get(DESCRIPTION);
}
public void setDescription(String description) {
set(DESCRIPTION, description);
}
public Resource getParent() {
DataObject parent = (DataObject)get(PARENT);
if (parent != null) {
return (Resource)DomainObjectFactory.newInstance(parent);
} else {
return null;
}
}
/**
* Set the parent of this resource.
*
* @param parent the parent of this resource
*/
public void setParent(Resource parent) {
set(PARENT, parent);
}
protected BigDecimal getParentResourceID() {
Resource parent = getParent();
if (parent != null) {
return parent.getID();
} else {
return null;
}
}
public boolean isRoot() {
return getParent() == null;
}
public String getPath() {
String path = (String) get(PATH);
if (null == path) {
throw new RuntimeException("null PATH for " + getName());
}
return path;
}
private void setPath(String path) {
set(PATH, path);
}
public abstract boolean isFolder();
public abstract boolean isFile();
public URL toURL() {
throw new UnsupportedOperationException();
}
/**
* Retrieves the path corresponding to a given resource.
*/
protected static String retrievePath(BigDecimal id) {
DataCollection collection = SessionManager.getSession().retrieve
(BASE_DATA_OBJECT_TYPE);
collection.addEqualsFilter(ID, id);
String ids = null;
if (collection.next()) {
ids = (String)collection.get(PATH);
collection.close();
} else {
// this means that the id is not valid so there is no path
return "";
}
StringBuffer ancestors = new StringBuffer();
if (ids == null) {
// this should not happen
return ancestors.toString();
}
collection = SessionManager.getSession().retrieve
(BASE_DATA_OBJECT_TYPE);
Filter filter = collection.addFilter(PATH + " <= :ancestors");
filter.set("ancestors", ids);
filter = collection.addFilter
(PATH + " = substr(:path, 1, length(" + PATH +"))");
filter.set("path", ids);
collection.addOrder(PATH);
while (collection.next()) {
ancestors.append(SEPARATOR + collection.get(NAME));
}
return ancestors.toString();
}
/**
* Verifies that the given string corresponds to a valid relative
* path name.
* @return 0 for a system-valid parthname, otherwise error codes
* defined in InvalidNameException.
* @see InvalidNameException
*/
protected static int isValidPath(String path) {
if (path.length() == 0) {
return InvalidNameException.ZERO_CHARACTERS_ERROR;
}
// check for leading slash
if (path.charAt(0) == SEPARATOR_CHAR) {
return InvalidNameException.LEADING_FILE_SEPARATOR_ERROR;
}
// check for trailing separator
if (path.charAt(path.length()-1) == SEPARATOR_CHAR) {
return InvalidNameException.TRAILING_FILE_SEPARATOR_ERROR;
}
// tokenize and validate each token
StringTokenizer st = new StringTokenizer(path, SEPARATOR);
while (st.hasMoreTokens()) {
String name = st.nextToken().trim();
if (name.length() == 0) {
return InvalidNameException.ZERO_CHARACTERS_ERROR;
}
int nec;
if ((nec = isValidName(name)) != 0 ) {
return nec;
}
}
return 0;
}
/**
* Verifies that the given string corresponds to a valid absolute
* path name.
*/
protected static boolean isAbsolutePath(String path) {
if (path.length() == 0) {
return false;
}
return (path.trim().charAt(0) == SEPARATOR_CHAR &&
0 == isValidPath(path.substring(1)));
}
/**
* Verifies that the string only contains valid characters for
* resource names. The following are allowed:
*
* [a-z][A-Z][0-9][-., ]
*
* In addition, names cannot begin with ".", i.e. we do NOT
* support file names like ".profile" at the moment.
* The current implementation does not allow international
* characters for resource names.
*
* @return 0 for a system-valid resource name, otherwise error codes
* defined in InvalidNameException.
* @see InvalidNameException
*/
protected static int isValidName(String name) {
Perl5Util util = new Perl5Util();
// check invalid characters at start of name
String INVALID_START_PATTERN = "/^[.]+/";
if (util.match(INVALID_START_PATTERN, name)) {
return InvalidNameException.LEADING_CHARACTER_ERROR;
}
// check invalid characters internal to name
String INVALID_NAME_PATTERN = "/[^a-zA-Z0-9\\_\\.\\-\\ ]+/";
if (util.match(INVALID_NAME_PATTERN, name)) {
return InvalidNameException.INVALID_CHARACTER_ERROR;
}
return 0;
}
/**
* Returns a canonical path by removing leading or trailing
* separator characters as needed.
*/
public static String getCanonicalPath(String path)
throws InvalidNameException {
String errMsg = "Unable to construct canonical path for: ";
if (null == path) {
throw new InvalidNameException(errMsg + path);
}
int start;
if (path.charAt(0) == SEPARATOR_CHAR) {
start = 1;
} else {
start = 0;
}
int end;
int length = path.length();
if (path.charAt(length-1) == SEPARATOR_CHAR) {
end = length-1;
} else {
end = length;
}
String canonicalPath = path.substring(start,end);
int pec;
if ( (pec=isValidPath(canonicalPath)) != 0) {
throw new InvalidNameException(pec);
}
return canonicalPath;
}
public BigDecimal getResourceID() {
return (BigDecimal)getID();
}
/**
* Retrieve the ID of a child resource using a relative path.
* This method is useful for checking the existence of a named
* child without the overhead of instantiating that child.
*
* @return the BigDecimal ID of the resource with the specified
* relative path
*/
public BigDecimal getResourceID(String path)
throws DataObjectNotFoundException, InvalidNameException {
int pec;
if ( (pec = isValidPath(path)) != 0) {
throw new InvalidNameException(pec);
}
String absPath = getPath() + SEPARATOR + path;
Session session = SessionManager.getSession();
DataQuery query = session.retrieveQuery
("com.arsdigita.docs.getResourceByPath");
query.setParameter("targetPath", absPath);
if (query.next()) {
BigDecimal id = (BigDecimal) query.get(ID);
query.close();
return id;
} else {
throw new DataObjectNotFoundException
("No resource with path " + absPath);
}
}
/**
* Return the list of declared property names for a Resource.
* This list only includes the properties that should be
* duplicated during a copy operation.
*/
protected Vector getPropertyNames() {
Vector names = new Vector();
names.addElement(NAME);
names.addElement(DESCRIPTION);
names.addElement(IS_FOLDER);
return names;
}
public java.util.Date getLastModifiedDate() {
java.util.Date date = (java.util.Date)get("lastModifiedDate");
return date;
}
public void setLastModifiedDate(java.util.Date date) {
set("lastModifiedDate",date);
}
public java.util.Date getCreationDate() {
java.util.Date date = (java.util.Date)get("creationDate");
return date;
}
public void setCreationDate(java.util.Date date) {
set("creationDate",date);
}
public User getCreationUser() {
DataObject dobj = (DataObject)get("creationUser");
if(dobj == null) {
throw new DataObjectNotFoundException("Creation User not found");
}
User user = (User)DomainObjectFactory.newInstance(dobj);
return user;
}
public void setCreationUser(User user) {
set("creationUser", user);
}
public User getLastModifiedUser() {
DataObject dobj = (DataObject)get("lastModifiedUser");
if(dobj == null) {
throw new DataObjectNotFoundException("Last User not found");
}
User user = (User)DomainObjectFactory.newInstance(dobj);
return user;
}
public void setLastModifiedUser(User user) {
set("lastModifiedUser", user);
}
public String getCreationIP() {
String ip = (String)get("creationIP");
return ip;
}
public void setCreationIP() {
String ip;
HttpServletRequest req = Web.getRequest();
if(req == null)
ip = "127.0.0.1";
else
ip = req.getRemoteAddr();
set("creationIP",ip);
}
public String getLastModifiedIP() {
String ip = (String)get("lastModifiedIP");
return ip;
}
public void setLastModifiedIP() {
String ip;
HttpServletRequest req = Web.getRequest();
if(req == null)
ip = "127.0.0.1";
else
ip = req.getRemoteAddr();
set("lastModifiedIP",ip);
}
/**
* Copy the generic properties of a Resource. Uses an explicit
* list of properties to copy as determined by {@link
* getPropertyNames}.
*/
protected static void copy(ResourceImpl src, ResourceImpl dest) {
Vector props = src.getPropertyNames();
for (int i = 0; i < props.size(); i++) {
String name = (String) props.elementAt(i);
dest.set(name, src.get(name));
}
}
public Resource copyTo(Resource parent) {
return copyTo(getName(), parent);
}
public Resource copyTo(String name) {
return copyTo(name, getParent());
}
public abstract Resource copyTo(String name, Resource parent);
/**
* Copy method implemented by extensions of this class. This
* method is protected because it isn't type safe, even though the
* BigDecimal ID of the parent resource is all that we really need
* to complete a copy.
*/
//protected abstract Resource copyTo(String name, BigDecimal parent);
}