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(); }