package org.exist.fluent;
import java.util.*;
import java.util.regex.*;
import org.exist.security.Permission;
/**
* A named resource in the contents tree of the database: either a folder or a document.
*
* @author <a href="mailto:piotr@ideanest.com">Piotr Kaminski</a>
*/
public abstract class NamedResource extends Resource {
/**
* The metadata facet of a named resource. Allows access to and manipulation of various aspects
* of a resource's metadata, including its permissions and various timestamps.
* NOTE: The permissions part of the interface is at a bare minimum right now, until I gather
* more use cases and flesh it out.
*/
public static abstract class MetadataFacet {
private static final Pattern INSTRUCTIONS_REGEX =
Pattern.compile("(a|(u?g?o?){1,3})((=r?w?u?)|([-+](r?w?u?){1,3}))(,(a|(u?g?o?){1,3})((=r?w?u?)|([-+](r?w?u?){1,3})))*");
private static final Pattern SEGMENT_REGEX = Pattern.compile("([augo]+)([-+=])([rwu]*)");
private Permission permissions;
protected MetadataFacet(Permission permissions) {
this.permissions = permissions;
}
/**
* Return the time at which this resource was originally created.
*
* @return the creation date of this resource
*/
public abstract Date creationDate();
/**
* Return the user who owns this resource for purposes of permission management.
*
* @return the owner of this resource
*/
public String owner() {return permissions.getOwner();}
/**
* Set the owner of this resource for purposes of permission management.
*
* @param owner the new owner of this resource
*/
public void owner(String owner) {permissions.setOwner(owner);}
/**
* Return the group who has privileged access to this resource for purposes of permission management.
*
* @return the owning group of this resource
*/
public String group() {return permissions.getOwnerGroup();}
/**
* Set the group that will have privileged access to this resource for purposes of permission management.
*
* @param group the new owning group of this resource
*/
public void group(String group) {permissions.setGroup(group);}
/**
* Return whether the given subject has the given permission. The "who" character refers to
* subjects as follows: <ul>
* <li>'u' stands for "user", the owner of the resource</li>
* <li>'g' stands for "group", the owning group of the resource</li>
* <li>'o' stands for "other", all users</li>
* <li>'a' stands for "all", a shortcut that refers to all 3 subjects above simultaneously</li></ul>
* The "what" character refers to the permissions to check: <ul>
* <li>'r' stands for read access</li>
* <li>'w' stands for write access</li>
* <li>'u' stands for update rights</li></ul>
*
* @param who the subject to check for permission
* @param what the access right to check for
* @return <code>true</code> if the given subject has the given permission, <code>false</code> otherwise
*/
public boolean hasPermission(char who, char what) {
int mask = convertPermissionBit(what);
switch(who) {
case 'a': mask = mask | (mask << 3) | (mask << 6);
case 'u': mask <<= 6; break;
case 'g': mask <<= 3; break;
case 'o': break;
default: throw new IllegalArgumentException("illegal permission \"who\" code '" + who + "'");
}
return (permissions.getPermissions() & mask) == mask;
}
private int convertPermissionBit(char what) {
switch(what) {
case 'r': return Permission.READ;
case 'w': return Permission.WRITE;
case 'u': return Permission.UPDATE;
default: throw new IllegalArgumentException("illegal permission \"what\" code '" + what + "'");
}
}
private int convertPermissionBits(String what) {
int perms = 0;
for (int i=0; i<what.length(); i++) perms |= convertPermissionBit(what.charAt(i));
return perms;
}
/**
* Change the permissions of the underlying resource. The format of the instructions is based upon
* that of the Unix chmod command: one or more letters representing the subject to modify, followed
* by an operation sign, followed by zero or more letters representing the permissions to modify. The
* subject and permission letters are the ones listed for {@link #hasPermission(char, char)}, except that
* the 'a' subject cannot be mixed with any of the other ones. If multiple subjects or permissions are
* listed, they must be listed in the canonical order shown. The operation signs are: <ul>
* <li>= to overwrite the permissions for the given subjects; if no permission letters follow the equals
* sign, rescind all permissions for the given subjects</li>
* <li>+ to grant additional permissions for the given subjects; at least one permission letter must be
* specified, and the given permissions will be added to any the subjects already possess</li>
* <li>- to rescind permissions from the given subjects; at least one permission letter must be specified,
* and the given permissions will be rescinded from the given subjects, without affecting any other
* permissions previously granted</li></ul>
* You can combine multiple such subjects/operation/permissions segments by separating them with
* commas (no spaces); they will be processed in the given order, so late segments can override the
* effects of earlier ones. Some examples:<ul>
* <li><tt>u-w</tt> rescinds the write permission from the owner, write-protecting the resource</li>
* <li><tt>ug+rw,o=</tt> grants read and write permissions to the owner and group, leaves the owner's
* and group's update permission set to its previous value, and rescinds all permissions from everybody else</li>
* <li><tt>a=r,u+w</tt> sets everyone to have only read permission, then grants write permission to just the
* owner</li></ul>
*
* @param instructions an instruction string encoding the desired changes to the permissions
*/
public void changePermissions(String instructions) {
if (!INSTRUCTIONS_REGEX.matcher(instructions).matches())
throw new IllegalArgumentException("bad permissions instructions: " + instructions);
StringTokenizer tokenizer = new StringTokenizer(instructions, ",");
while (tokenizer.hasMoreTokens()) {
Matcher matcher = SEGMENT_REGEX.matcher(tokenizer.nextToken());
if (!matcher.matches()) throw new RuntimeException("internal error: illegal segment got through syntax regex, instruction string " + instructions);
int perms = convertPermissionBits(matcher.group(3));
int mask = 0;
boolean all = matcher.group(1).equals("a");
if (all || matcher.group(1).indexOf('u') != -1) mask |= perms << 6;
if (all || matcher.group(1).indexOf('g') != -1) mask |= perms << 3;
if (all || matcher.group(1).indexOf('o') != -1) mask |= perms;
int newPerms;
switch(matcher.group(2).charAt(0)) {
case '=': newPerms = mask; break;
case '+': newPerms = permissions.getPermissions() | mask;
case '-': newPerms = permissions.getPermissions() & ~mask;
default: throw new RuntimeException("internal error: illegal segment operator got through syntax regex, instruction string " + instructions);
}
permissions.setPermissions(newPerms);
}
}
public String toString() {
StringBuilder buf = new StringBuilder();
if (permissions.getUserPermissions() == permissions.getGroupPermissions()
&& permissions.getUserPermissions() == permissions.getPublicPermissions()) {
appendPermissions('a', 'u', buf); buf.append(' ');
} else {
appendPermissions('u', 'u', buf); buf.append(',');
appendPermissions('g', 'g', buf); buf.append(',');
appendPermissions('o', 'o', buf); buf.append(' ');
}
if (owner() != null) buf.append("u:").append(owner()).append(' ');
if (group() != null) buf.append("g:").append(group()).append(' ');
buf.append(creationDate());
return buf.toString();
}
private void appendPermissions(char prefix, char who, StringBuilder buf) {
buf.append(prefix).append('=');
if (hasPermission(who, 'r')) buf.append('r');
if (hasPermission(who, 'w')) buf.append('w');
if (hasPermission(who, 'u')) buf.append('u');
}
}
protected NamedResource(NamespaceMap namespaceBindings, Database db) {
super(namespaceBindings, db);
}
/**
* Return the local name of this resource. This name will never contain slashes.
*
* @return the local name of this resource
*/
public abstract String name();
/**
* Return the absolute path of this resource. This is the path of its parent folder
* plus its local name.
*
* @return the absolute path of this resource
*/
public abstract String path();
/**
* Copy this resource to another location, potentially changing the copy's name in
* the process.
*
* @param destination the destination folder for the copy
* @param name the desired name for the copy
* @return the new copy of the resource
*/
public abstract NamedResource copy(Folder destination, Name name);
/**
* Move this resource to another collection, potentially changing its name in the process.
* This object will refer to the resource in its new location after this method returns.
*
* @param destination the destination folder for the move
* @param name the desired name for the moved resource
*/
public abstract void move(Folder destination, Name name);
/**
* Delete this resource from the database.
*/
public abstract void delete();
/**
* Return the metadata facet for this resource, which lets you read and manipulate
* metadata such as ownership, access permissions, and creation/modification timestamps.
*
* @return the metadata facet for this resource
*/
public abstract MetadataFacet metadata();
}