FELIX-514 Updated compendium bundle to R4.1 OSGi API
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@681945 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/org.osgi.compendium/src/main/java/info/dmtree/Acl.java b/org.osgi.compendium/src/main/java/info/dmtree/Acl.java
new file mode 100644
index 0000000..b550d8d
--- /dev/null
+++ b/org.osgi.compendium/src/main/java/info/dmtree/Acl.java
@@ -0,0 +1,576 @@
+/*
+ * $Header: /cvshome/build/info.dmtree/src/info/dmtree/Acl.java,v 1.6 2006/07/12 21:21:37 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package info.dmtree;
+
+import java.util.*;
+
+/**
+ * <code>Acl</code> is an immutable class representing structured access to
+ * DMT ACLs. Under OMA DM the ACLs are defined as strings with an internal
+ * syntax.
+ * <p>
+ * The methods of this class taking a principal as parameter accept remote
+ * server IDs (as passed to
+ * {@link DmtAdmin#getSession(String, String, int) DmtAdmin.getSession}), as
+ * well as " <code>*</code> " indicating any principal.
+ * <p>
+ * The syntax for valid remote server IDs:<br>
+ * <<i>server-identifier</i>> ::= All printable characters except
+ * <code>'='</code>, <code>'&'</code>, <code>'*'</code>, <code>'+'</code>
+ * or white-space characters.
+ */
+public final class Acl {
+
+ // ----- Public constants -----//
+
+ /**
+ * Principals holding this permission can issue GET command on the node
+ * having this ACL.
+ */
+ public static final int GET = 1;
+
+ /**
+ * Principals holding this permission can issue ADD commands on the node
+ * having this ACL.
+ */
+ public static final int ADD = 2;
+
+ /**
+ * Principals holding this permission can issue REPLACE commands on the node
+ * having this ACL.
+ */
+ public static final int REPLACE = 4;
+
+ /**
+ * Principals holding this permission can issue DELETE commands on the node
+ * having this ACL.
+ */
+ public static final int DELETE = 8;
+
+ /**
+ * Principals holding this permission can issue EXEC commands on the node
+ * having this ACL.
+ */
+ public static final int EXEC = 16;
+
+ /**
+ * Principals holding this permission can issue any command on the node
+ * having this ACL. This permission is the logical OR of {@link #ADD},
+ * {@link #DELETE}, {@link #EXEC}, {@link #GET} and {@link #REPLACE}
+ * permissions.
+ */
+ public static final int ALL_PERMISSION = ADD | DELETE | EXEC | GET
+ | REPLACE;
+
+ // ----- Private constants -----//
+
+ private static final int[] PERMISSION_CODES = new int[] { ADD, DELETE,
+ EXEC, GET, REPLACE };
+
+ private static final String[] PERMISSION_NAMES = new String[] { "Add",
+ "Delete", "Exec", "Get", "Replace" };
+
+ private static final String ALL_PRINCIPALS = "*";
+
+ // ----- Private fields -----//
+
+ // the implementation takes advantage of this being a sorted map
+ private final TreeMap principalPermissions;
+
+ private final int globalPermissions;
+
+ // ----- Public constructors -----//
+
+ /**
+ * Create an instance of the ACL from its canonic string representation.
+ *
+ * @param acl The string representation of the ACL as defined in OMA DM. If
+ * <code>null</code> or empty then it represents an empty list of
+ * principals with no permissions.
+ * @throws IllegalArgumentException if acl is not a valid OMA DM ACL string
+ */
+ public Acl(String acl) {
+ if (acl == null || acl.equals("")) { // empty permission set
+ principalPermissions = new TreeMap();
+ globalPermissions = 0;
+ return;
+ }
+
+ TreeMap tempPrincipalPermissions = new TreeMap();
+ int tempGlobalPermissions = 0;
+
+ String[] aclEntries = split(acl, '&', -1);
+ for (int i = 0; i < aclEntries.length; i++) {
+ if (aclEntries[i].length() == 0)
+ throw new IllegalArgumentException(
+ "Invalid ACL string: empty ACL entry.");
+
+ String[] entryParts = split(aclEntries[i], '=', 2);
+ if (entryParts.length == 1)
+ throw new IllegalArgumentException(
+ "Invalid ACL string: no '=' in ACL entry.");
+ if (entryParts[1].length() == 0)
+ throw new IllegalArgumentException(
+ "Invalid ACL string: no server identifiers in ACL entry.");
+
+ int command = parseCommand(entryParts[0]);
+ String[] serverIds = split(entryParts[1], '+', -1);
+ for (int j = 0; j < serverIds.length; j++) {
+ if (serverIds[j].length() == 0)
+ throw new IllegalArgumentException(
+ "Invalid ACL string: empty server identifier.");
+
+ if (serverIds[j].equals(ALL_PRINCIPALS))
+ tempGlobalPermissions |= command;
+ else {
+ checkServerId(serverIds[j], "Invalid ACL string: "
+ + "server ID contains illegal character");
+ Integer n = (Integer) tempPrincipalPermissions
+ .get(serverIds[j]);
+ int oldPermission = (n != null) ? n.intValue() : 0;
+ tempPrincipalPermissions.put(serverIds[j], new Integer(
+ oldPermission | command));
+ }
+ }
+ }
+
+ principalPermissions = tempPrincipalPermissions;
+ globalPermissions = tempGlobalPermissions;
+ }
+
+ /**
+ * Creates an instance with a specified list of principals and the
+ * permissions they hold. The two arrays run in parallel, that is
+ * <code>principals[i]</code> will hold <code>permissions[i]</code> in
+ * the ACL.
+ * <p>
+ * A principal name may not appear multiple times in the 'principals'
+ * argument. If the "*" principal appears in the array, the
+ * corresponding permissions will be granted to all principals (regardless
+ * of whether they appear in the array or not).
+ *
+ * @param principals The array of principals
+ * @param permissions The array of permissions
+ * @throws IllegalArgumentException if the length of the two arrays are not
+ * the same, if any array element is invalid, or if a principal
+ * appears multiple times in the <code>principals</code> array
+ */
+ public Acl(String[] principals, int[] permissions) {
+ if (principals.length != permissions.length)
+ throw new IllegalArgumentException(
+ "The lengths of the principal and permission arrays are not the same.");
+
+ TreeMap tempPrincipalPermissions = new TreeMap();
+ int tempGlobalPermissions = 0;
+
+ for (int i = 0; i < principals.length; i++) {
+ // allow one * in 'principals' array, remove after loop
+ if (!ALL_PRINCIPALS.equals(principals[i]))
+ checkPrincipal(principals[i]);
+ checkPermissions(permissions[i]);
+
+ Integer permInt = new Integer(permissions[i]);
+ Object old = tempPrincipalPermissions.put(principals[i], permInt);
+ if (old != null)
+ throw new IllegalArgumentException("Principal '"
+ + principals[i]
+ + "' appears multiple times in the principal array.");
+ }
+
+ // set the global permissions if there was a * in the array
+ Object globalPermObj = tempPrincipalPermissions.remove(ALL_PRINCIPALS);
+ if (globalPermObj != null)
+ tempGlobalPermissions = ((Integer) globalPermObj).intValue();
+
+ principalPermissions = tempPrincipalPermissions;
+ globalPermissions = tempGlobalPermissions;
+ }
+
+ // ----- Private constructors -----//
+
+ /**
+ * Creates an instance identical to the <code>base</code> ACL except for
+ * the permissions of the given <code>principal</code>, which are
+ * overwritten with the given <code>permissions</code>.
+ * <p>
+ * Assumes that the permissions parameter has been checked. All
+ * modifications of an <code>Acl</code> (add, delete, set) are done
+ * through this method.
+ *
+ * @param base The ACL that provides all permissions except for permissions
+ * of the given principal.
+ * @param principal The entity to which permission should be granted.
+ * @param permissions The set of permissions to be given. The parameter can
+ * be a logical <code>or</code> of the permission constants defined
+ * in this class.
+ */
+ private Acl(Acl base, String principal, int permissions) {
+ // make a shallow copy of the permission table, the keys (String) and
+ // values (Integer) are immutable anyway
+ TreeMap tempPrincipalPermissions = (TreeMap) base.principalPermissions
+ .clone();
+ int tempGlobalPermissions = base.globalPermissions;
+
+ int deletedGlobalPerm = tempGlobalPermissions & ~permissions;
+ if (ALL_PRINCIPALS.equals(principal)) {
+ deleteFromAll(tempPrincipalPermissions, deletedGlobalPerm);
+ tempGlobalPermissions = permissions;
+ } else {
+ checkPrincipal(principal);
+
+ if (deletedGlobalPerm != 0)
+ throw new IllegalArgumentException(
+ "Cannot revoke globally set permissions ("
+ + writeCommands(deletedGlobalPerm)
+ + ") from a specific principal (" + principal
+ + ").");
+
+ setPrincipalPermission(tempPrincipalPermissions, principal,
+ permissions);
+ }
+
+ principalPermissions = tempPrincipalPermissions;
+ globalPermissions = tempGlobalPermissions;
+ }
+
+ // ----- Public methods -----//
+
+ /**
+ * Checks whether the given object is equal to this <code>Acl</code>
+ * instance. Two <code>Acl</code> instances are equal if they allow the
+ * same set of permissions for the same set of principals.
+ *
+ * @param obj the object to compare with this <code>Acl</code> instance
+ * @return <code>true</code> if the parameter represents the same ACL as
+ * this instance
+ */
+ public boolean equals(Object obj) {
+ if (obj == this)
+ return true;
+
+ if (!(obj instanceof Acl))
+ return false;
+
+ Acl other = (Acl) obj;
+
+ if (globalPermissions != other.globalPermissions
+ || principalPermissions.size() != other.principalPermissions
+ .size())
+ return false;
+
+ // principalPermissions sets cannot be easily compared, because they are
+ // not canonical: the global permissions may or may not be present for
+ // each principal, without changing the meaning of the Acl object.
+
+ // Compare canonical string representations, inefficient but simple.
+ return toString().equals(other.toString());
+ }
+
+ /**
+ * Returns the hash code for this ACL instance. If two <code>Acl</code>
+ * instances are equal according to the {@link #equals} method, then calling
+ * this method on each of them must produce the same integer result.
+ *
+ * @return hash code for this ACL
+ */
+ public int hashcode() {
+ // Using the hash code of the canonical string representation, because
+ // the principalPermissions set is not canonical (see above).
+ return toString().hashCode();
+ }
+
+ /**
+ * Create a new <code>Acl</code> instance from this <code>Acl</code> with
+ * the given permission added for the given principal. The already existing
+ * permissions of the principal are not affected.
+ *
+ * @param principal The entity to which permissions should be granted, or
+ * "*" to grant permissions to all principals.
+ * @param permissions The permissions to be given. The parameter can be a
+ * logical <code>or</code> of more permission constants defined in
+ * this class.
+ * @return a new <code>Acl</code> instance
+ * @throws IllegalArgumentException if <code>principal</code> is not a
+ * valid principal name or if <code>permissions</code> is not a
+ * valid combination of the permission constants defined in this
+ * class
+ */
+ public synchronized Acl addPermission(String principal, int permissions) {
+ checkPermissions(permissions);
+
+ int oldPermissions = getPermissions(principal);
+ return setPermission(principal, oldPermissions | permissions);
+ }
+
+ /**
+ * Create a new <code>Acl</code> instance from this <code>Acl</code> with
+ * the given permission revoked from the given principal. Other permissions
+ * of the principal are not affected.
+ * <p>
+ * Note, that it is not valid to revoke a permission from a specific
+ * principal if that permission is granted globally to all principals.
+ *
+ * @param principal The entity from which permissions should be revoked, or
+ * "*" to revoke permissions from all principals.
+ * @param permissions The permissions to be revoked. The parameter can be a
+ * logical <code>or</code> of more permission constants defined in
+ * this class.
+ * @return a new <code>Acl</code> instance
+ * @throws IllegalArgumentException if <code>principal</code> is not a
+ * valid principal name, if <code>permissions</code> is not a
+ * valid combination of the permission constants defined in this
+ * class, or if a globally granted permission would have been
+ * revoked from a specific principal
+ */
+ public synchronized Acl deletePermission(String principal, int permissions) {
+ checkPermissions(permissions);
+
+ int oldPermissions = getPermissions(principal);
+ return setPermission(principal, oldPermissions & ~permissions);
+ }
+
+ /**
+ * Get the permissions associated to a given principal.
+ *
+ * @param principal The entity whose permissions to query, or "*"
+ * to query the permissions that are granted globally, to all
+ * principals
+ * @return The permissions of the given principal. The returned
+ * <code>int</code> is a bitmask of the permission constants defined
+ * in this class
+ * @throws IllegalArgumentException if <code>principal</code> is not a
+ * valid principal name
+ */
+ public synchronized int getPermissions(String principal) {
+ int permissions = 0;
+
+ if (!(ALL_PRINCIPALS.equals(principal))) {
+ checkPrincipal(principal);
+ Object po = principalPermissions.get(principal);
+ if (po != null)
+ permissions = ((Integer) po).intValue();
+ }
+
+ return permissions | globalPermissions;
+ }
+
+ /**
+ * Check whether the given permissions are granted to a certain principal.
+ * The requested permissions are specified as a bitfield, for example
+ * <code>(Acl.ADD | Acl.DELETE | Acl.GET)</code>.
+ *
+ * @param principal The entity to check, or "*" to check whether
+ * the given permissions are granted to all principals globally
+ * @param permissions The permissions to check
+ * @return <code>true</code> if the principal holds all the given permissions
+ * @throws IllegalArgumentException if <code>principal</code> is not a
+ * valid principal name or if <code>permissions</code> is not a
+ * valid combination of the permission constants defined in this
+ * class
+ */
+ public synchronized boolean isPermitted(String principal, int permissions) {
+ checkPermissions(permissions);
+
+ int hasPermissions = getPermissions(principal);
+ return (permissions & hasPermissions) == permissions;
+ }
+
+ /**
+ * Create a new <code>Acl</code> instance from this <code>Acl</code> where
+ * all permissions for the given principal are overwritten with the given
+ * permissions.
+ * <p>
+ * Note, that when changing the permissions of a specific principal, it is
+ * not allowed to specify a set of permissions stricter than the global set
+ * of permissions (that apply to all principals).
+ *
+ * @param principal The entity to which permissions should be granted, or
+ * "*" to globally grant permissions to all principals.
+ * @param permissions The set of permissions to be given. The parameter is
+ * a bitmask of the permission constants defined in this class.
+ * @return a new <code>Acl</code> instance
+ * @throws IllegalArgumentException if <code>principal</code> is not a
+ * valid principal name, if <code>permissions</code> is not a
+ * valid combination of the permission constants defined in this
+ * class, or if a globally granted permission would have been
+ * revoked from a specific principal
+ */
+ public synchronized Acl setPermission(String principal, int permissions) {
+ checkPermissions(permissions);
+
+ Acl newPermission = new Acl(this, principal, permissions);
+ return newPermission;
+ }
+
+ /**
+ * Get the list of principals who have any kind of permissions on this node.
+ * The list only includes those principals that have been explicitly
+ * assigned permissions (so "*" is never returned), globally set
+ * permissions naturally apply to all other principals as well.
+ *
+ * @return The array of principals having permissions on this node.
+ */
+ public String[] getPrincipals() {
+ return (String[]) (principalPermissions.keySet().toArray(new String[0]));
+ }
+
+ /**
+ * Give the canonic string representation of this ACL. The operations are in
+ * the following order: {Add, Delete, Exec, Get, Replace}, principal names
+ * are sorted alphabetically.
+ *
+ * @return The string representation as defined in OMA DM.
+ */
+ public synchronized String toString() {
+ String acl = null;
+ for (int i = 0; i < PERMISSION_CODES.length; i++)
+ acl = writeEntry(PERMISSION_CODES[i], acl);
+
+ return (acl != null) ? acl : "";
+ }
+
+ // ----- Private utility methods -----//
+
+ private String writeEntry(int command, String acl) {
+ String aclEntry = null;
+
+ if ((command & globalPermissions) > 0)
+ aclEntry = ALL_PRINCIPALS;
+ else {
+ // TreeMap guarantees alphabetical ordering of keys during traversal
+ Iterator i = principalPermissions.entrySet().iterator();
+ while (i.hasNext()) {
+ Map.Entry entry = (Map.Entry) i.next();
+ if ((command & ((Integer) entry.getValue()).intValue()) > 0)
+ aclEntry = appendEntry(aclEntry, '+', (String) entry
+ .getKey());
+ }
+ }
+
+ if (aclEntry == null)
+ return acl;
+
+ return appendEntry(acl, '&', writeCommands(command) + '=' + aclEntry);
+ }
+
+ private static void deleteFromAll(TreeMap principalPermissions, int perm) {
+ Iterator i = principalPermissions.entrySet().iterator();
+ while (i.hasNext()) {
+ Map.Entry entry = (Map.Entry) i.next();
+ setPrincipalPermission(principalPermissions, (String) entry
+ .getKey(), ((Integer) entry.getValue()).intValue() & ~perm);
+ }
+ }
+
+ private static void setPrincipalPermission(TreeMap principalPermissions,
+ String principal, int perm) {
+ if (perm == 0)
+ principalPermissions.remove(principal);
+ else
+ principalPermissions.put(principal, new Integer(perm));
+ }
+
+ private static String writeCommands(int command) {
+ String commandStr = null;
+ for (int i = 0; i < PERMISSION_CODES.length; i++)
+ if ((command & PERMISSION_CODES[i]) != 0)
+ commandStr = appendEntry(commandStr, ',', PERMISSION_NAMES[i]);
+
+ return (commandStr != null) ? commandStr : "";
+ }
+
+ private static String appendEntry(String base, char separator, String entry) {
+ return (base != null) ? base + separator + entry : entry;
+ }
+
+ private static int parseCommand(String command) {
+ int i = Arrays.asList(PERMISSION_NAMES).indexOf(command);
+ if (i == -1)
+ throw new IllegalArgumentException(
+ "Invalid ACL string: unknown command '" + command + "'.");
+
+ return PERMISSION_CODES[i];
+ }
+
+ private static void checkPermissions(int perm) {
+ if ((perm & ~ALL_PERMISSION) != 0)
+ throw new IllegalArgumentException("Invalid ACL permission value: "
+ + perm);
+ }
+
+ private static void checkPrincipal(String principal) {
+ if (principal == null)
+ throw new IllegalArgumentException("Principal is null.");
+
+ checkServerId(principal, "Principal name contains illegal character");
+ }
+
+ private static void checkServerId(String serverId, String errorText) {
+ char[] chars = serverId.toCharArray();
+ for (int i = 0; i < chars.length; i++)
+ if ("*=+&".indexOf(chars[i]) != -1
+ || Character.isWhitespace(chars[i]))
+ throw new IllegalArgumentException(errorText + " '" + chars[i]
+ + "'.");
+ }
+
+ private static String[] split(String input, char sep, int limit) {
+ Vector v = new Vector();
+ boolean limited = (limit > 0);
+ int applied = 0;
+ int index = 0;
+ StringBuffer part = new StringBuffer();
+
+ while (index < input.length()) {
+ char ch = input.charAt(index);
+ if (ch != sep)
+ part.append(ch);
+ else {
+ ++applied;
+ v.add(part.toString());
+ part = new StringBuffer();
+ }
+ ++index;
+ if (limited && applied == limit - 1)
+ break;
+ }
+ while (index < input.length()) {
+ char ch = input.charAt(index);
+ part.append(ch);
+ ++index;
+ }
+ v.add(part.toString());
+
+ int last = v.size();
+ if (0 == limit) {
+ for (int j = v.size() - 1; j >= 0; --j) {
+ String s = (String) v.elementAt(j);
+ if ("".equals(s))
+ --last;
+ else
+ break;
+ }
+ }
+
+ String[] ret = new String[last];
+ for (int i = 0; i < last; ++i)
+ ret[i] = (String) v.elementAt(i);
+
+ return ret;
+ }
+}
diff --git a/org.osgi.compendium/src/main/java/info/dmtree/DmtAdmin.java b/org.osgi.compendium/src/main/java/info/dmtree/DmtAdmin.java
new file mode 100644
index 0000000..abfcc6f
--- /dev/null
+++ b/org.osgi.compendium/src/main/java/info/dmtree/DmtAdmin.java
@@ -0,0 +1,281 @@
+/*
+ * $Header: /cvshome/build/info.dmtree/src/info/dmtree/DmtAdmin.java,v 1.9 2006/07/11 16:59:41 tszeredi Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package info.dmtree;
+
+/**
+ * An interface providing methods to open sessions and register listeners. The
+ * implementation of <code>DmtAdmin</code> should register itself in the OSGi
+ * service registry as a service. <code>DmtAdmin</code> is the entry point for
+ * applications to use the DMT API.
+ * <p>
+ * The <code>getSession</code> methods are used to open a session on a
+ * specified subtree of the DMT. A typical way of usage:
+ * <pre>
+ * serviceRef = context.getServiceReference(DmtAdmin.class.getName());
+ * DmtAdmin admin = (DmtAdmin) context.getService(serviceRef);
+ * DmtSession session = admin.getSession("./OSGi/Configuration");
+ * session.createInteriorNode("./OSGi/Configuration/my.table");
+ * </pre>
+ * <p>
+ * The methods for opening a session take a node URI (the session root) as a
+ * parameter. All segments of the given URI must be within the segment length
+ * limit of the implementation, and the special characters '/' and '\' must be
+ * escaped (preceded by a '\'). Any string can be converted to a valid URI
+ * segment using the {@link Uri#mangle(String)} method.
+ * <p>
+ * It is possible to specify a lock mode when opening the session (see lock type
+ * constants in {@link DmtSession}). This determines whether the session can
+ * run in parallel with other sessions, and the kinds of operations that can be
+ * performed in the session. All Management Objects constituting the device
+ * management tree must support read operations on their nodes, while support
+ * for write operations depends on the Management Object. Management Objects
+ * supporting write access may support transactional write, non-transactional
+ * write or both. Users of <code>DmtAdmin</code> should consult the Management
+ * Object specification and implementation for the supported update modes. If
+ * Management Object definition permits, implementations are encouraged to
+ * support both update modes.
+ * <p>
+ * This interface also contains methods for manipulating the set of
+ * <code>DmtEventListener</code> objects that are called when the structure or
+ * content of the tree is changed. These methods are not needed in an OSGi
+ * environment, clients should register listeners through the Event Admin
+ * service.
+ */
+public interface DmtAdmin {
+ /**
+ * Opens a <code>DmtSession</code> for local usage on a given subtree of
+ * the DMT with non transactional write lock. This call is equivalent to the
+ * following:
+ * <code>getSession(null, subtreeUri, DmtSession.LOCK_TYPE_EXCLUSIVE)</code>
+ * <p>
+ * The <code>subtreeUri</code> parameter must contain an absolute URI. It
+ * can also be <code>null</code>, in this case the session is opened with
+ * the default session root, ".", that gives access to the whole
+ * tree.
+ * <p>
+ * To perform this operation the caller must have <code>DmtPermission</code>
+ * for the <code>subtreeUri</code> node with the Get action present.
+ *
+ * @param subtreeUri the subtree on which DMT manipulations can be performed
+ * within the returned session
+ * @return a <code>DmtSession</code> object for the requested subtree
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>URI_TOO_LONG</code> if <code>subtreeUri</code> or
+ * a segment of it is too long, or if it has too many segments
+ * <li><code>INVALID_URI</code> if <code>subtreeUri</code> is
+ * syntactically invalid
+ * <li><code>NODE_NOT_FOUND</code> if <code>subtreeUri</code>
+ * specifies a non-existing node
+ * <li><code>SESSION_CREATION_TIMEOUT</code> if the operation
+ * timed out because of another ongoing session
+ * <li><code>COMMAND_FAILED</code> if <code>subtreeUri</code>
+ * specifies a relative URI, or some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws SecurityException if the caller does not have
+ * <code>DmtPermission</code> for the given root node with the Get
+ * action present
+ */
+ DmtSession getSession(String subtreeUri) throws DmtException;
+
+ /**
+ * Opens a <code>DmtSession</code> for local usage on a specific DMT
+ * subtree with a given lock mode. This call is equivalent to the
+ * following: <code>getSession(null, subtreeUri, lockMode)</code>
+ * <p>
+ * The <code>subtreeUri</code> parameter must contain an absolute URI. It
+ * can also be <code>null</code>, in this case the session is opened with
+ * the default session root, ".", that gives access to the whole
+ * tree.
+ * <p>
+ * To perform this operation the caller must have <code>DmtPermission</code>
+ * for the <code>subtreeUri</code> node with the Get action present.
+ *
+ * @param subtreeUri the subtree on which DMT manipulations can be performed
+ * within the returned session
+ * @param lockMode one of the lock modes specified in
+ * <code>DmtSession</code>
+ * @return a <code>DmtSession</code> object for the requested subtree
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>URI_TOO_LONG</code> if <code>subtreeUri</code> or
+ * a segment of it is too long, or if it has too many segments
+ * <li><code>INVALID_URI</code> if <code>subtreeUri</code> is
+ * syntactically invalid
+ * <li><code>NODE_NOT_FOUND</code> if <code>subtreeUri</code>
+ * specifies a non-existing node
+ * <li><code>FEATURE_NOT_SUPPORTED</code> if atomic sessions are
+ * not supported by the implementation and <code>lockMode</code>
+ * requests an atomic session
+ * <li><code>SESSION_CREATION_TIMEOUT</code> if the operation
+ * timed out because of another ongoing session
+ * <li><code>COMMAND_FAILED</code> if <code>subtreeUri</code>
+ * specifies a relative URI, if <code>lockMode</code> is unknown,
+ * or some unspecified error is encountered while attempting to
+ * complete the command
+ * </ul>
+ * @throws SecurityException if the caller does not have
+ * <code>DmtPermission</code> for the given root node with the Get
+ * action present
+ */
+ DmtSession getSession(String subtreeUri, int lockMode) throws DmtException;
+
+ /**
+ * Opens a <code>DmtSession</code> on a specific DMT subtree using a
+ * specific lock mode on behalf of a remote principal. If local management
+ * applications are using this method then they should provide
+ * <code>null</code> as the first parameter. Alternatively they can use
+ * other forms of this method without providing a principal string.
+ * <p>
+ * The <code>subtreeUri</code> parameter must contain an absolute URI. It
+ * can also be <code>null</code>, in this case the session is opened with
+ * the default session root, ".", that gives access to the whole
+ * tree.
+ * <p>
+ * This method is guarded by <code>DmtPrincipalPermission</code> in case of
+ * remote sessions. In addition, the caller must have Get access rights
+ * (ACL in case of remote sessions, <code>DmtPermission</code> in case of
+ * local sessions) on the <code>subtreeUri</code> node to perform this
+ * operation.
+ *
+ * @param principal the identifier of the remote server on whose behalf the
+ * data manipulation is performed, or <code>null</code> for local
+ * sessions
+ * @param subtreeUri the subtree on which DMT manipulations can be performed
+ * within the returned session
+ * @param lockMode one of the lock modes specified in
+ * <code>DmtSession</code>
+ * @return a <code>DmtSession</code> object for the requested subtree
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>URI_TOO_LONG</code> if <code>subtreeUri</code> or
+ * a segment of it is too long, or if it has too many segments
+ * <li><code>INVALID_URI</code> if <code>subtreeUri</code> is
+ * syntactically invalid
+ * <li><code>NODE_NOT_FOUND</code> if <code>subtreeUri</code>
+ * specifies a non-existing node
+ * <li><code>PERMISSION_DENIED</code> if <code>principal</code> is
+ * not <code>null</code> and the ACL of the node does not allow the
+ * <code>Get</code> operation for the principal on the given root
+ * node
+ * <li><code>FEATURE_NOT_SUPPORTED</code> if atomic sessions are
+ * not supported by the implementation and <code>lockMode</code>
+ * requests an atomic session
+ * <li><code>SESSION_CREATION_TIMEOUT</code> if the operation
+ * timed out because of another ongoing session
+ * <li><code>COMMAND_FAILED</code> if <code>subtreeUri</code>
+ * specifies a relative URI, if <code>lockMode</code> is unknown,
+ * or some unspecified error is encountered while attempting to
+ * complete the command
+ * </ul>
+ * @throws SecurityException in case of remote sessions, if the caller does
+ * not have the required <code>DmtPrincipalPermission</code> with a
+ * target matching the <code>principal</code> parameter, or in case
+ * of local sessions, if the caller does not have
+ * <code>DmtPermission</code> for the given root node with the Get
+ * action present
+ */
+ DmtSession getSession(String principal, String subtreeUri, int lockMode)
+ throws DmtException;
+
+ /**
+ * Registers an event listener on behalf of a local application. The given
+ * listener will receive notification on all changes affecting the specified
+ * subtree. The subtree is specified by its root node URI. An event is
+ * delivered to the registered listener if at least one affected node is
+ * within this subtree. The events can also be filtered by specifying a
+ * bitmask of relevant event types (e.g.
+ * <code>DmtEvent.ADDED | DmtEvent.REPLACED | DmtEvent.SESSION_CLOSED</code>).
+ * Only event types included in the bitmask will be delivered to the
+ * listener.
+ * <p>
+ * The listener will only receive the change notifications of nodes for
+ * which the registering application has the appropriate GET
+ * {@link info.dmtree.security.DmtPermission}.
+ * <p>
+ * If the specified <code>listener</code> was already registered, calling
+ * this method will update the registration.
+ *
+ * @param type a bitmask of event types the caller is interested in
+ * @param uri the URI of the root node of a subtree, must not be
+ * <code>null</code>
+ * @param listener the listener to be registered, must not be
+ * <code>null</code>
+ * @throws SecurityException if the caller doesn't have the necessary GET
+ * <code>DmtPermission</code> for the given URI
+ * @throws NullPointerException if the <code>uri</code> or
+ * <code>listener</code> parameter is <code>null</code>
+ * @throws IllegalArgumentException if the <code>type</code> parameter
+ * contains invalid bits (not corresponding to any event type
+ * defined in <code>DmtEvent</code>), or if the <code>uri</code>
+ * parameter is invalid (is not an absolute URI or is syntactically
+ * incorrect)
+ */
+ void addEventListener(int type, String uri, DmtEventListener listener);
+
+ /**
+ * Registers an event listener on behalf of a remote principal. The given
+ * listener will receive notification on all changes affecting the specified
+ * subtree. The subtree is specified by its root node URI. An event is
+ * delivered to the registered listener if at least one affected node is
+ * within this subtree. The events can also be filtered by specifying a
+ * bitmask of relevant event types (e.g.
+ * <code>DmtEvent.ADDED | DmtEvent.REPLACED | DmtEvent.SESSION_CLOSED</code>).
+ * Only event types included in the bitmask will be delivered to the
+ * listener.
+ * <p>
+ * The listener will only receive the change notifications of nodes for
+ * which the node ACL grants GET access to the specified principal.
+ * <p>
+ * If the specified <code>listener</code> was already registered, calling
+ * this method will update the registration.
+ *
+ * @param principal the management server identity the caller is acting on
+ * behalf of, must not be <code>null</code>
+ * @param type a bitmask of event types the caller is interested in
+ * @param uri the URI of the root node of a subtree, must not be
+ * <code>null</code>
+ * @param listener the listener to be registered, must not be
+ * <code>null</code>
+ * @throws SecurityException if the caller doesn't have the necessary
+ * <code>DmtPrincipalPermission</code> to use the specified
+ * principal
+ * @throws NullPointerException if the <code>principal</code>,
+ * <code>uri</code> or <code>listener</code> parameter is
+ * <code>null</code>
+ * @throws IllegalArgumentException if the <code>type</code> parameter
+ * contains invalid bits (not corresponding to any event type
+ * defined in <code>DmtEvent</code>), or if the <code>uri</code>
+ * parameter is invalid (is not an absolute URI or is syntactically
+ * incorrect)
+ */
+ void addEventListener(String principal, int type, String uri,
+ DmtEventListener listener);
+
+ /**
+ * Remove a previously registered listener. After this call, the listener
+ * will not receive change notifications.
+ *
+ * @param listener the listener to be unregistered, must not be
+ * <code>null</code>
+ * @throws NullPointerException if the <code>listener</code> parameter is
+ * <code>null</code>
+ */
+ void removeEventListener(DmtEventListener listener);
+}
diff --git a/org.osgi.compendium/src/main/java/info/dmtree/DmtData.java b/org.osgi.compendium/src/main/java/info/dmtree/DmtData.java
new file mode 100644
index 0000000..51c5cae
--- /dev/null
+++ b/org.osgi.compendium/src/main/java/info/dmtree/DmtData.java
@@ -0,0 +1,907 @@
+/*
+ * $Header: /cvshome/build/info.dmtree/src/info/dmtree/DmtData.java,v 1.8 2006/07/10 21:37:07 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package info.dmtree;
+
+import java.util.Arrays;
+import java.util.Hashtable;
+
+// Possible enhancements to this class:
+// * new constructors and get/set methods for b64, to access the encoded value
+// * new constructors and get/set methods for date/time, for more convenient
+// Java access
+/**
+ * An immutable data structure representing the contents of a leaf or interior
+ * node. This structure represents only the value and the format property of the
+ * node, all other properties (like MIME type) can be set and read using the
+ * <code>DmtSession</code> interface.
+ * <p>
+ * Different constructors are available to create nodes with different formats.
+ * Nodes of <code>null</code> format can be created using the static
+ * {@link #NULL_VALUE} constant instance of this class.
+ * <p>
+ * {@link #FORMAT_RAW_BINARY} and {@link #FORMAT_RAW_STRING} enable the support
+ * of future data formats. When using these formats, the actual format name is
+ * specified as a <code>String</code>. The application is responsible for the
+ * proper encoding of the data according to the specified format.
+ */
+public final class DmtData {
+
+ /**
+ * The node holds an OMA DM <code>int</code> value.
+ */
+ public static final int FORMAT_INTEGER = 0x0001;
+
+ /**
+ * The node holds an OMA DM <code>float</code> value.
+ */
+ public static final int FORMAT_FLOAT = 0x0002;
+
+ /**
+ * The node holds an OMA DM <code>chr</code> value.
+ */
+ public static final int FORMAT_STRING = 0x0004;
+
+ /**
+ * The node holds an OMA DM <code>bool</code> value.
+ */
+ public static final int FORMAT_BOOLEAN = 0x0008;
+
+ /**
+ * The node holds an OMA DM <code>date</code> value.
+ */
+ public static final int FORMAT_DATE = 0x0010;
+
+ /**
+ * The node holds an OMA DM <code>time</code> value.
+ */
+ public static final int FORMAT_TIME = 0x0020;
+
+ /**
+ * The node holds an OMA DM <code>bin</code> value. The value of the node
+ * corresponds to the Java <code>byte[]</code> type.
+ */
+ public static final int FORMAT_BINARY = 0x0040;
+
+ /**
+ * The node holds an OMA DM <code>b64</code> value. Like
+ * {@link #FORMAT_BINARY}, this format is also represented by the Java
+ * <code>byte[]</code> type, the difference is only in the corresponding
+ * OMA DM format.
+ */
+ public static final int FORMAT_BASE64 = 0x0080;
+
+ /**
+ * The node holds an OMA DM <code>xml</code> value.
+ */
+ public static final int FORMAT_XML = 0x0100;
+
+ /**
+ * The node holds an OMA DM <code>null</code> value. This corresponds to
+ * the Java <code>null</code> type.
+ */
+ public static final int FORMAT_NULL = 0x0200;
+
+ /**
+ * Format specifier of an internal node. An interior node can hold a Java
+ * object as value (see {@link DmtData#DmtData(Object)} and
+ * {@link DmtData#getNode()}). This value can be used by Java programs that
+ * know a specific URI understands the associated Java type. This type is
+ * further used as a return value of the {@link MetaNode#getFormat} method
+ * for interior nodes.
+ */
+ public static final int FORMAT_NODE = 0x0400;
+
+ /**
+ * The node holds raw protocol data encoded as <code>String</code>. The
+ * {@link #getFormatName()} method can be used to get the actual format
+ * name.
+ */
+ public static final int FORMAT_RAW_STRING = 0x0800;
+
+ /**
+ * The node holds raw protocol data encoded in binary format. The
+ * {@link #getFormatName()} method can be used to get the actual format
+ * name.
+ */
+ public static final int FORMAT_RAW_BINARY = 0x1000;
+
+
+ private static final Hashtable FORMAT_NAMES = new Hashtable();
+
+ static {
+ FORMAT_NAMES.put(new Integer(FORMAT_BASE64), "b64");
+ FORMAT_NAMES.put(new Integer(FORMAT_BINARY), "bin");
+ FORMAT_NAMES.put(new Integer(FORMAT_BOOLEAN), "bool");
+ FORMAT_NAMES.put(new Integer(FORMAT_DATE), "date");
+ FORMAT_NAMES.put(new Integer(FORMAT_FLOAT), "float");
+ FORMAT_NAMES.put(new Integer(FORMAT_INTEGER), "int");
+ FORMAT_NAMES.put(new Integer(FORMAT_NODE), "node");
+ FORMAT_NAMES.put(new Integer(FORMAT_NULL), "null");
+ FORMAT_NAMES.put(new Integer(FORMAT_STRING), "chr");
+ FORMAT_NAMES.put(new Integer(FORMAT_TIME), "time");
+ FORMAT_NAMES.put(new Integer(FORMAT_XML), "xml");
+ }
+
+ /**
+ * Constant instance representing a leaf node of <code>null</code> format.
+ */
+ public static final DmtData NULL_VALUE = new DmtData();
+ // FORMAT_NAMES must be initialized by the time the constr. is called
+
+ private final String str;
+
+ private final int integer;
+
+ private final float flt;
+
+ private final boolean bool;
+
+ private final byte[] bytes;
+
+ private final int format;
+
+ private final String formatName;
+
+ private final Object complex;
+
+ /**
+ * Create a <code>DmtData</code> instance of <code>null</code> format.
+ * This constructor is private and used only to create the public
+ * {@link #NULL_VALUE} constant.
+ */
+ private DmtData() {
+ format = FORMAT_NULL;
+ formatName = getFormatName(format);
+
+ this.str = null;
+ this.integer = 0;
+ this.flt = 0;
+ this.bool = false;
+ this.bytes = null;
+ this.complex = null;
+ }
+
+ /**
+ * Create a <code>DmtData</code> instance of <code>chr</code> format
+ * with the given string value. The <code>null</code> string argument is
+ * valid.
+ *
+ * @param str the string value to set
+ */
+ public DmtData(String str) {
+ format = FORMAT_STRING;
+ formatName = getFormatName(format);
+ this.str = str;
+
+ this.integer = 0;
+ this.flt = 0;
+ this.bool = false;
+ this.bytes = null;
+ this.complex = null;
+ }
+
+ /**
+ * Create a <code>DmtData</code> instance of <code>node</code> format
+ * with the given object value. The value represents complex data associated
+ * with an interior node.
+ * <p>
+ * Certain interior nodes can support access to their subtrees through such
+ * complex values, making it simpler to retrieve or update all leaf nodes in
+ * a subtree.
+ * <p>
+ * The given value must be a non-<code>null</code> immutable object.
+ *
+ * @param complex the complex data object to set
+ */
+ public DmtData(Object complex) {
+ if(complex == null)
+ throw new NullPointerException("Complex data argument is null.");
+
+ format = FORMAT_NODE;
+ formatName = getFormatName(format);
+ this.complex = complex;
+
+ this.str = null;
+ this.integer = 0;
+ this.flt = 0;
+ this.bool = false;
+ this.bytes = null;
+ }
+
+ /**
+ * Create a <code>DmtData</code> instance of the specified format and set
+ * its value based on the given string. Only the following string-based
+ * formats can be created using this constructor:
+ * <ul>
+ * <li>{@link #FORMAT_STRING} - value can be any string
+ * <li>{@link #FORMAT_XML} - value must contain an XML fragment (the
+ * validity is not checked by this constructor)
+ * <li>{@link #FORMAT_DATE} - value must be parseable to an ISO 8601
+ * calendar date in complete representation, basic format (pattern
+ * <tt>CCYYMMDD</tt>)
+ * <li>{@link #FORMAT_TIME} - value must be parseable to an ISO 8601 time
+ * of day in either local time, complete representation, basic format
+ * (pattern <tt>hhmmss</tt>) or Coordinated Universal Time, basic format
+ * (pattern <tt>hhmmssZ</tt>)
+ * </ul>
+ * The <code>null</code> string argument is only valid if the format is
+ * string or XML.
+ *
+ * @param value the string, XML, date or time value to set
+ * @param format the format of the <code>DmtData</code> instance to be
+ * created, must be one of the formats specified above
+ * @throws IllegalArgumentException if <code>format</code> is not one of
+ * the allowed formats, or <code>value</code> is not a valid
+ * string for the given format
+ * @throws NullPointerException if a date or time is constructed and
+ * <code>value</code> is <code>null</code>
+ */
+ public DmtData(String value, int format) {
+ switch (format) {
+ case FORMAT_DATE:
+ checkDateFormat(value);
+ break;
+ case FORMAT_TIME:
+ checkTimeFormat(value);
+ break;
+ case FORMAT_STRING:
+ case FORMAT_XML:
+ break; // nothing to do, all string values are accepted
+ default:
+ throw new IllegalArgumentException(
+ "Invalid format in string constructor: " + format);
+ }
+ this.format = format;
+ this.formatName = getFormatName(format);
+ this.str = value;
+
+ this.integer = 0;
+ this.flt = 0;
+ this.bool = false;
+ this.bytes = null;
+ this.complex = null;
+ }
+
+ /**
+ * Create a <code>DmtData</code> instance of <code>int</code> format and
+ * set its value.
+ *
+ * @param integer the integer value to set
+ */
+ public DmtData(int integer) {
+ format = FORMAT_INTEGER;
+ formatName = getFormatName(format);
+ this.integer = integer;
+
+ this.str = null;
+ this.flt = 0;
+ this.bool = false;
+ this.bytes = null;
+ this.complex = null;
+ }
+
+ /**
+ * Create a <code>DmtData</code> instance of <code>float</code> format
+ * and set its value.
+ *
+ * @param flt the float value to set
+ */
+ public DmtData(float flt) {
+ format = FORMAT_FLOAT;
+ formatName = getFormatName(format);
+ this.flt = flt;
+
+ this.str = null;
+ this.integer = 0;
+ this.bool = false;
+ this.bytes = null;
+ this.complex = null;
+ }
+
+ /**
+ * Create a <code>DmtData</code> instance of <code>bool</code> format
+ * and set its value.
+ *
+ * @param bool the boolean value to set
+ */
+ public DmtData(boolean bool) {
+ format = FORMAT_BOOLEAN;
+ formatName = getFormatName(format);
+ this.bool = bool;
+
+ this.str = null;
+ this.integer = 0;
+ this.flt = 0;
+ this.bytes = null;
+ this.complex = null;
+ }
+
+ /**
+ * Create a <code>DmtData</code> instance of <code>bin</code> format and
+ * set its value.
+ *
+ * @param bytes the byte array to set, must not be <code>null</code>
+ * @throws NullPointerException if <code>bytes</code> is <code>null</code>
+ */
+ public DmtData(byte[] bytes) {
+ if (bytes == null)
+ throw new NullPointerException("Binary data argument is null.");
+
+ format = FORMAT_BINARY;
+ formatName = getFormatName(format);
+ this.bytes = bytes;
+
+ this.str = null;
+ this.integer = 0;
+ this.flt = 0;
+ this.bool = false;
+ this.complex = null;
+ }
+
+ /**
+ * Create a <code>DmtData</code> instance of <code>bin</code> or
+ * <code>b64</code> format and set its value. The chosen format is
+ * specified by the <code>base64</code> parameter.
+ *
+ * @param bytes the byte array to set, must not be <code>null</code>
+ * @param base64 if <code>true</code>, the new instance will have
+ * <code>b64</code> format, if <code>false</code>, it will have
+ * <code>bin</code> format
+ * @throws NullPointerException if <code>bytes</code> is <code>null</code>
+ */
+ public DmtData(byte[] bytes, boolean base64) {
+ if (bytes == null)
+ throw new NullPointerException("Binary data argument is null.");
+
+ format = base64 ? FORMAT_BASE64 : FORMAT_BINARY;
+ formatName = getFormatName(format);
+ this.bytes = bytes;
+
+ this.str = null;
+ this.integer = 0;
+ this.flt = 0;
+ this.bool = false;
+ this.complex = null;
+ }
+
+ /**
+ * Create a <code>DmtData</code> instance in {@link #FORMAT_RAW_STRING}
+ * format. The data is provided encoded as a <code>String</code>. The
+ * actual data format is specified in <code>formatName</code>. The
+ * encoding used in <code>data</code> must conform to this format.
+ *
+ * @param formatName the name of the format, must not be <code>null</code>
+ * @param data the data encoded according to the specified format, must not
+ * be <code>null</code>
+ * @throws NullPointerException if <code>formatName</code> or
+ * <code>data</code> is <code>null</code>
+ */
+ public DmtData(String formatName, String data) {
+ if(formatName == null)
+ throw new NullPointerException("Format name argument is null.");
+ if(data == null)
+ throw new NullPointerException("Data argument is null.");
+
+ format = FORMAT_RAW_STRING;
+ this.formatName = formatName;
+ this.str = data;
+
+ this.bytes = null;
+ this.integer = 0;
+ this.flt = 0;
+ this.bool = false;
+ this.complex = null;
+ }
+
+ /**
+ * Create a <code>DmtData</code> instance in {@link #FORMAT_RAW_BINARY}
+ * format. The data is provided encoded as binary. The actual data format is
+ * specified in <code>formatName</code>. The encoding used in
+ * <code>data</code> must conform to this format.
+ *
+ * @param formatName the name of the format, must not be <code>null</code>
+ * @param data the data encoded according to the specified format, must not
+ * be <code>null</code>
+ * @throws NullPointerException if <code>formatName</code> or
+ * <code>data</code> is <code>null</code>
+ */
+ public DmtData(String formatName, byte[] data) {
+ if(formatName == null)
+ throw new NullPointerException("Format name argument is null.");
+ if(data == null)
+ throw new NullPointerException("Data argument is null.");
+
+ format = FORMAT_RAW_BINARY;
+ this.formatName = formatName;
+ this.bytes = (byte[]) data.clone();
+
+ this.str = null;
+ this.integer = 0;
+ this.flt = 0;
+ this.bool = false;
+ this.complex = null;
+ }
+
+ /**
+ * Gets the value of a node with string (<code>chr</code>) format.
+ *
+ * @return the string value
+ * @throws DmtIllegalStateException if the format of the node is not string
+ */
+ public String getString() {
+ if (format == FORMAT_STRING)
+ return str;
+
+ throw new DmtIllegalStateException("DmtData value is not string.");
+ }
+
+ /**
+ * Gets the value of a node with date format. The returned date string is
+ * formatted according to the ISO 8601 definition of a calendar date in
+ * complete representation, basic format (pattern <tt>CCYYMMDD</tt>).
+ *
+ * @return the date value
+ * @throws DmtIllegalStateException if the format of the node is not date
+ */
+ public String getDate() {
+ if (format == FORMAT_DATE)
+ return str;
+
+ throw new DmtIllegalStateException("DmtData value is not date.");
+ }
+
+ /**
+ * Gets the value of a node with time format. The returned time string is
+ * formatted according to the ISO 8601 definition of the time of day. The
+ * exact format depends on the value the object was initialized with: either
+ * local time, complete representation, basic format (pattern
+ * <tt>hhmmss</tt>) or Coordinated Universal Time, basic format (pattern
+ * <tt>hhmmssZ</tt>).
+ *
+ * @return the time value
+ * @throws DmtIllegalStateException if the format of the node is not time
+ */
+ public String getTime() {
+ if (format == FORMAT_TIME)
+ return str;
+
+ throw new DmtIllegalStateException("DmtData value is not time.");
+ }
+
+ /**
+ * Gets the value of a node with <code>xml</code> format.
+ *
+ * @return the XML value
+ * @throws DmtIllegalStateException if the format of the node is not
+ * <code>xml</code>
+ */
+ public String getXml() {
+ if (format == FORMAT_XML)
+ return str;
+
+ throw new DmtIllegalStateException("DmtData value is not XML.");
+ }
+
+ /**
+ * Gets the value of a node with integer (<code>int</code>) format.
+ *
+ * @return the integer value
+ * @throws DmtIllegalStateException if the format of the node is not integer
+ */
+ public int getInt() {
+ if (format == FORMAT_INTEGER)
+ return integer;
+
+ throw new DmtIllegalStateException("DmtData value is not integer.");
+ }
+
+ /**
+ * Gets the value of a node with <code>float</code> format.
+ *
+ * @return the float value
+ * @throws DmtIllegalStateException if the format of the node is not
+ * <code>float</code>
+ */
+ public float getFloat() {
+ if (format == FORMAT_FLOAT)
+ return flt;
+
+ throw new DmtIllegalStateException("DmtData value is not float.");
+ }
+
+ /**
+ * Gets the value of a node with boolean (<code>bool</code>) format.
+ *
+ * @return the boolean value
+ * @throws DmtIllegalStateException if the format of the node is not boolean
+ */
+ public boolean getBoolean() {
+ if (format == FORMAT_BOOLEAN)
+ return bool;
+
+ throw new DmtIllegalStateException("DmtData value is not boolean.");
+ }
+
+ /**
+ * Gets the value of a node with binary (<code>bin</code>) format.
+ *
+ * @return the binary value
+ * @throws DmtIllegalStateException if the format of the node is not binary
+ */
+ public byte[] getBinary() {
+ if (format == FORMAT_BINARY) {
+ byte[] bytesCopy = new byte[bytes.length];
+ for (int i = 0; i < bytes.length; i++)
+ bytesCopy[i] = bytes[i];
+
+ return bytesCopy;
+ }
+
+ throw new DmtIllegalStateException("DmtData value is not a byte array.");
+ }
+
+ /**
+ * Gets the value of a node in raw binary ({@link #FORMAT_RAW_BINARY})
+ * format.
+ *
+ * @return the data value in raw binary format
+ * @throws DmtIllegalStateException if the format of the node is not raw binary
+ */
+ public byte[] getRawBinary() {
+ if (format == FORMAT_RAW_BINARY)
+ return (byte[]) bytes.clone();
+
+ throw new DmtIllegalStateException(
+ "DmtData value is not in raw binary format.");
+ }
+
+ /**
+ * Gets the value of a node in raw <code>String</code>
+ * ({@link #FORMAT_RAW_STRING}) format.
+ *
+ * @return the data value in raw <code>String</code> format
+ * @throws DmtIllegalStateException if the format of the node is not raw
+ * <code>String</code>
+ */
+ public String getRawString() {
+ if (format == FORMAT_RAW_STRING)
+ return str;
+
+ throw new DmtIllegalStateException(
+ "DmtData value is not in raw string format.");
+ }
+
+ /**
+ * Gets the value of a node with base 64 (<code>b64</code>) format.
+ *
+ * @return the binary value
+ * @throws DmtIllegalStateException if the format of the node is not base 64.
+ */
+ public byte[] getBase64() {
+ if (format == FORMAT_BASE64) {
+ byte[] bytesCopy = new byte[bytes.length];
+ for (int i = 0; i < bytes.length; i++)
+ bytesCopy[i] = bytes[i];
+
+ return bytesCopy;
+ }
+
+ throw new DmtIllegalStateException(
+ "DmtData value is not in base 64 format.");
+ }
+
+ /**
+ * Gets the complex data associated with an interior node (<code>node</code>
+ * format).
+ * <p>
+ * Certain interior nodes can support access to their subtrees through
+ * complex values, making it simpler to retrieve or update all leaf nodes in
+ * the subtree.
+ *
+ * @return the data object associated with an interior node
+ * @throws DmtIllegalStateException if the format of the data is not
+ * <code>node</code>
+ */
+ public Object getNode() {
+ if(format == FORMAT_NODE)
+ return complex;
+
+ throw new DmtIllegalStateException(
+ "DmtData does not contain interior node data.");
+ }
+
+ /**
+ * Get the node's format, expressed in terms of type constants defined in
+ * this class. Note that the 'format' term is a legacy from OMA DM, it is
+ * more customary to think of this as 'type'.
+ *
+ * @return the format of the node
+ */
+ public int getFormat() {
+ return format;
+ }
+
+ /**
+ * Returns the format of this <code>DmtData</code> as <code>String</code>.
+ * For the predefined data formats this is the OMA DM defined name of the
+ * format. For {@link #FORMAT_RAW_STRING} and {@link #FORMAT_RAW_BINARY}
+ * this is the format specified when the object was created.
+ *
+ * @return the format name as <code>String</code>
+ */
+ public String getFormatName() {
+ return formatName;
+ }
+
+ /**
+ * Get the size of the data. The returned value depends on the format of
+ * data in the node:
+ * <ul>
+ * <li>{@link #FORMAT_STRING}, {@link #FORMAT_XML}, {@link #FORMAT_BINARY},
+ * {@link #FORMAT_BASE64}, {@link #FORMAT_RAW_STRING}, and
+ * {@link #FORMAT_RAW_BINARY}: the length of the stored data, or 0 if
+ * the data is <code>null</code>
+ * <li>{@link #FORMAT_INTEGER} and {@link #FORMAT_FLOAT}: 4
+ * <li>{@link #FORMAT_DATE} and {@link #FORMAT_TIME}: the length of the
+ * date or time in its string representation
+ * <li>{@link #FORMAT_BOOLEAN}: 1
+ * <li>{@link #FORMAT_NODE}: -1 (unknown)
+ * <li>{@link #FORMAT_NULL}: 0
+ * </ul>
+ *
+ * @return the size of the data stored by this object
+ */
+ public int getSize() {
+ switch (format) {
+ case FORMAT_STRING:
+ case FORMAT_XML:
+ case FORMAT_DATE:
+ case FORMAT_TIME:
+ case FORMAT_RAW_STRING:
+ return str == null ? 0 : str.length();
+ case FORMAT_BINARY:
+ case FORMAT_BASE64:
+ case FORMAT_RAW_BINARY:
+ return bytes.length;
+ case FORMAT_INTEGER:
+ case FORMAT_FLOAT:
+ return 4;
+ case FORMAT_BOOLEAN:
+ return 1;
+ case FORMAT_NODE:
+ return -1;
+ case FORMAT_NULL:
+ return 0;
+ }
+
+ return 0; // never reached
+ }
+
+ /**
+ * Gets the string representation of the <code>DmtData</code>. This
+ * method works for all formats.
+ * <p>
+ * For string format data - including {@link #FORMAT_RAW_STRING} - the
+ * string value itself is returned, while for XML, date, time, integer,
+ * float, boolean and node formats the string form of the value is returned.
+ * Binary - including {@link #FORMAT_RAW_BINARY} - and base64 data is
+ * represented by two-digit hexadecimal numbers for each byte separated by
+ * spaces. The {@link #NULL_VALUE} data has the string form of
+ * "<code>null</code>". Data of string or XML format containing the Java
+ * <code>null</code> value is represented by an empty string.
+ *
+ * @return the string representation of this <code>DmtData</code> instance
+ */
+ public String toString() {
+ switch (format) {
+ case FORMAT_STRING:
+ case FORMAT_XML:
+ case FORMAT_DATE:
+ case FORMAT_TIME:
+ case FORMAT_RAW_STRING:
+ return str == null ? "" : str;
+ case FORMAT_INTEGER:
+ return String.valueOf(integer);
+ case FORMAT_FLOAT:
+ return String.valueOf(flt);
+ case FORMAT_BOOLEAN:
+ return String.valueOf(bool);
+ case FORMAT_BINARY:
+ case FORMAT_BASE64:
+ case FORMAT_RAW_BINARY:
+ return getHexDump(bytes);
+ case FORMAT_NODE:
+ return complex.toString();
+ case FORMAT_NULL:
+ return "null";
+ }
+
+ return null; // never reached
+ }
+
+ /**
+ * Compares the specified object with this <code>DmtData</code> instance.
+ * Two <code>DmtData</code> objects are considered equal if their format
+ * is the same, and their data (selected by the format) is equal.
+ * <p>
+ * In case of {@link #FORMAT_RAW_BINARY} and {@link #FORMAT_RAW_STRING}
+ * the textual name of the data format - as returned by
+ * {@link #getFormatName()} - must be equal as well.
+ *
+ * @param obj the object to compare with this <code>DmtData</code>
+ * @return true if the argument represents the same <code>DmtData</code>
+ * as this object
+ */
+ public boolean equals(Object obj) {
+ if (!(obj instanceof DmtData))
+ return false;
+
+ DmtData other = (DmtData) obj;
+
+ if (format != other.format)
+ return false;
+
+ switch (format) {
+ case FORMAT_STRING:
+ case FORMAT_XML:
+ case FORMAT_DATE:
+ case FORMAT_TIME:
+ return str == null ? other.str == null : str.equals(other.str);
+ case FORMAT_INTEGER:
+ return integer == other.integer;
+ case FORMAT_FLOAT:
+ return flt == other.flt;
+ case FORMAT_BOOLEAN:
+ return bool == other.bool;
+ case FORMAT_BINARY:
+ case FORMAT_BASE64:
+ return Arrays.equals(bytes, other.bytes);
+ case FORMAT_NODE:
+ return complex.equals(other.complex);
+ case FORMAT_NULL:
+ return true;
+ case FORMAT_RAW_BINARY:
+ return formatName.equals(other.formatName)
+ && Arrays.equals(bytes, other.bytes);
+ case FORMAT_RAW_STRING:
+ // in this case str cannot be null
+ return formatName.equals(other.formatName) && str.equals(other.str);
+ }
+
+ return false; // never reached
+ }
+
+ /**
+ * Returns the hash code value for this <code>DmtData</code> instance. The
+ * hash code is calculated based on the data (selected by the format) of
+ * this object.
+ *
+ * @return the hash code value for this object
+ */
+ public int hashCode() {
+ switch (format) {
+ case FORMAT_STRING:
+ case FORMAT_XML:
+ case FORMAT_DATE:
+ case FORMAT_TIME:
+ case FORMAT_RAW_STRING:
+ return str == null ? 0 : str.hashCode();
+ case FORMAT_INTEGER:
+ return new Integer(integer).hashCode();
+ case FORMAT_FLOAT:
+ return new Float(flt).hashCode();
+ case FORMAT_BOOLEAN:
+ return new Boolean(bool).hashCode();
+ case FORMAT_BINARY:
+ case FORMAT_BASE64:
+ case FORMAT_RAW_BINARY:
+ return new String(bytes).hashCode();
+ case FORMAT_NODE:
+ return complex.hashCode();
+ case FORMAT_NULL:
+ return 0;
+ }
+
+ return 0; // never reached
+ }
+
+ private static void checkDateFormat(String value) {
+ if(value.length() != 8)
+ throw new IllegalArgumentException("Date string '" + value +
+ "' does not follow the format 'CCYYMMDD'.");
+
+ int year = checkNumber(value, "Date", 0, 4, 0, 9999);
+ int month = checkNumber(value, "Date", 4, 2, 1, 12);
+ int day = checkNumber(value, "Date", 6, 2, 1, 31);
+
+ // Date checking is not prepared for all special rules (for example
+ // historical leap years), production code could contain a full check.
+
+ // Day 31 is invalid for April, June, September and November
+ if((month == 4 || month == 6 || month == 9 || month == 11) && day == 31)
+ throw new IllegalArgumentException("Date string '" + value +
+ "' contains an invalid date.");
+
+ // February 29 is invalid except for leap years, Feb. 30-31 are invalid
+ if(month == 2 && day > 28 &&
+ !(day == 29 && year%4 == 0 && (year%100 != 0 || year%400 == 0)))
+ throw new IllegalArgumentException("Date string '" + value +
+ "' contains an invalid date.");
+ }
+
+ private static void checkTimeFormat(String value) {
+ if(value.length() > 0 && value.charAt(value.length()-1) == 'Z')
+ value = value.substring(0, value.length()-1);
+
+ if(value.length() != 6)
+ throw new IllegalArgumentException("Time string '" + value +
+ "' does not follow the format 'hhmmss' or 'hhmmssZ'.");
+
+ // Time checking is not prepared for all special rules (for example
+ // leap seconds), production code could contain a full check.
+
+ // if hour is 24, only 240000 should be allowed
+ checkNumber(value, "Time", 0, 2, 0, 24);
+ checkNumber(value, "Time", 2, 2, 0, 59);
+ checkNumber(value, "Time", 4, 2, 0, 59);
+
+ if(value.startsWith("24") && !value.startsWith("240000"))
+ throw new IllegalArgumentException("Time string is out of range.");
+ }
+
+ private static int checkNumber(String value, String name, int from,
+ int length, int min, int max) {
+ String part = value.substring(from, from+length);
+ int number;
+ try {
+ number = Integer.parseInt(part);
+ } catch(NumberFormatException e) {
+ throw new IllegalArgumentException(name + " string '" + value +
+ "' contains a non-numeric part.");
+ }
+ if(number < min || number > max)
+ throw new IllegalArgumentException("A segment of the " + name +
+ " string '" + value + "' is out of range.");
+
+ return number;
+ }
+
+ // character array of hexadecimal digits, used for printing binary data
+ private static char[] hex = "0123456789ABCDEF".toCharArray();
+
+ // generates a hexadecimal dump of the given binary data
+ private static String getHexDump(byte[] bytes) {
+ if (bytes.length == 0)
+ return "";
+
+ StringBuffer buf = new StringBuffer();
+ appendHexByte(buf, bytes[0]);
+ for (int i = 1; i < bytes.length; i++)
+ appendHexByte(buf.append(' '), bytes[i]);
+
+ return buf.toString();
+ }
+
+ private static void appendHexByte(StringBuffer buf, byte b) {
+ buf.append(hex[(b & 0xF0) >> 4]).append(hex[b & 0x0F]);
+ }
+
+ private static String getFormatName(int format) {
+ return (String) FORMAT_NAMES.get(new Integer(format));
+ }
+}
diff --git a/org.osgi.compendium/src/main/java/info/dmtree/DmtEvent.java b/org.osgi.compendium/src/main/java/info/dmtree/DmtEvent.java
new file mode 100644
index 0000000..dac4f1d
--- /dev/null
+++ b/org.osgi.compendium/src/main/java/info/dmtree/DmtEvent.java
@@ -0,0 +1,140 @@
+/*
+ * $Header: /cvshome/build/info.dmtree/src/info/dmtree/DmtEvent.java,v 1.8 2006/07/04 12:12:16 tszeredi Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package info.dmtree;
+
+/**
+ * Event class storing the details of a change in the tree.
+ * <code>DmtEvent</code> is used by <code>DmtAdmin</code> to notify registered
+ * {@link DmtEventListener EventListeners} about important changes. Events are
+ * generated after every successful DMT change, and also when sessions are
+ * opened or closed. If a {@link DmtSession} is opened in atomic mode, DMT
+ * events are only sent when the session is committed, when the changes are
+ * actually performed.
+ * <p>
+ * An event is generated for each group of nodes added, deleted, replaced,
+ * renamed or copied, in this order. Events are also generated when sessions
+ * are opened and closed.
+ * <p>
+ * The <code>type</code> of the event describes the change that triggered the
+ * event delivery. Each event carries the unique identifier of the session in
+ * which the described change happened. The events describing changes in the DMT
+ * carry the list of affected nodes. In case of {@link #COPIED} or
+ * {@link #RENAMED} events, the event carries the list of new nodes as well.
+ * <p>
+ * When a <code>DmtEvent</code> is delivered to a listener, the event contains
+ * only those node URIs that the listener has access to. This access control
+ * decision is based on the principal specified when the listener was
+ * registered:
+ * <ul>
+ * <li> If the listener was registered specifying an explicit principal, using
+ * the {@link DmtAdmin#addEventListener(String, int, String, DmtEventListener)}
+ * method, then the target node ACLs should be checked for providing GET access
+ * to the specified principal;
+ * <li> When the listener was registered without an explicit principal then the
+ * listener needs GET {@link info.dmtree.security.DmtPermission} for
+ * the corresponding node.
+ * </ul>
+ */
+public interface DmtEvent {
+
+ /**
+ * Event type indicating nodes that were added.
+ */
+ int ADDED = 0x01;
+
+ /**
+ * Event type indicating nodes that were copied.
+ */
+ int COPIED = 0x02;
+
+ /**
+ * Event type indicating nodes that were deleted.
+ */
+ int DELETED = 0x04;
+
+ /**
+ * Event type indicating nodes that were renamed.
+ */
+ int RENAMED = 0x08;
+
+ /**
+ * Event type indicating nodes that were replaced.
+ */
+ int REPLACED = 0x10;
+
+ /**
+ * Event type indicating that a new session was opened.
+ */
+ int SESSION_OPENED = 0x20;
+
+ /**
+ * Event type indicating that a session was closed. This type of event is
+ * sent when the session is closed by the client or becomes inactive for any
+ * other reason (session timeout, fatal errors in business methods, etc.).
+ */
+ int SESSION_CLOSED = 0x40;
+
+ /**
+ * This method returns the type of this event.
+ *
+ * @return the type of this event.
+ */
+ int getType();
+
+ /**
+ * This method returns the identifier of the session in which this event
+ * took place. The ID is guaranteed to be unique on a machine.
+ *
+ * @return the unique indetifier of the session that triggered the event
+ */
+ int getSessionId();
+
+ /**
+ * This method can be used to query the subject nodes of this event. The
+ * method returns <code>null</code> for {@link #SESSION_OPENED} and
+ * {@link #SESSION_CLOSED}.
+ * <p>
+ * The method returns only those affected nodes that the caller has the GET
+ * permission for (or in case of {@link #COPIED} or {@link #RENAMED} events,
+ * where the caller has GET permissions for either the source or the
+ * destination nodes). Therefore, it is possible that the method returns an
+ * empty array. All returned URIs are absolute.
+ *
+ * @return the array of affected nodes
+ * @see #getNewNodes
+ */
+ String[] getNodes();
+
+ /**
+ * This method can be used to query the new nodes, when the type of the
+ * event is {@link #COPIED} or {@link #RENAMED}. For all other event types
+ * this method returns <code>null</code>.
+ * <p>
+ * The array returned by this method runs parallel to the array returned by
+ * {@link #getNodes}, the elements in the two arrays contain the source and
+ * destination URIs for the renamed or copied nodes in the same order. All
+ * returned URIs are absolute.
+ * <p>
+ * This method returns only those nodes where the caller has the GET
+ * permission for the source or destination node of the operation.
+ * Therefore, it is possible that the method returns an empty array.
+ *
+ * @return the array of newly created nodes
+ */
+ String[] getNewNodes();
+}
diff --git a/org.osgi.compendium/src/main/java/info/dmtree/DmtEventListener.java b/org.osgi.compendium/src/main/java/info/dmtree/DmtEventListener.java
new file mode 100644
index 0000000..c5425b7
--- /dev/null
+++ b/org.osgi.compendium/src/main/java/info/dmtree/DmtEventListener.java
@@ -0,0 +1,37 @@
+/*
+ * $Header: /cvshome/build/info.dmtree/src/info/dmtree/DmtEventListener.java,v 1.6 2006/07/04 12:12:16 tszeredi Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package info.dmtree;
+
+/**
+ * Registered implementations of this class are notified via {@link DmtEvent}
+ * objects about important changes in the tree. Events are generated after every
+ * successful DMT change, and also when sessions are opened or closed. If a
+ * {@link DmtSession} is opened in atomic mode, DMT events are only sent when
+ * the session is committed, when the changes are actually performed.
+ */
+public interface DmtEventListener {
+
+ /**
+ * <code>DmtAdmin</code> uses this method to notify the registered
+ * listeners about the change. This method is called asynchronously from the
+ * actual event occurrence.
+ *
+ * @param event the <code>DmtEvent</code> describing the change in detail
+ */
+ void changeOccurred(DmtEvent event);
+}
diff --git a/org.osgi.compendium/src/main/java/info/dmtree/DmtException.java b/org.osgi.compendium/src/main/java/info/dmtree/DmtException.java
new file mode 100644
index 0000000..c723bc0
--- /dev/null
+++ b/org.osgi.compendium/src/main/java/info/dmtree/DmtException.java
@@ -0,0 +1,639 @@
+/*
+ * $Header: /cvshome/build/info.dmtree/src/info/dmtree/DmtException.java,v 1.9 2006/07/12 21:21:37 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package info.dmtree;
+
+import java.io.PrintStream;
+import java.util.Vector;
+
+/**
+ * Checked exception received when a DMT operation fails. Beside the exception
+ * message, a <code>DmtException</code> always contains an error code (one of
+ * the constants specified in this class), and may optionally contain the URI of
+ * the related node, and information about the cause of the exception.
+ * <p>
+ * Some of the error codes defined in this class have a corresponding error code
+ * defined in OMA DM, in these cases the name and numerical value from OMA DM is
+ * used. Error codes without counterparts in OMA DM were given numbers from a
+ * different range, starting from 1.
+ * <p>
+ * The cause of the exception (if specified) can either be a single
+ * <code>Throwable</code> instance, or a list of such instances if several
+ * problems occurred during the execution of a method. An example for the latter
+ * is the <code>close</code> method of <code>DmtSession</code> that tries to
+ * close multiple plugins, and has to report the exceptions of all failures.
+ * <p>
+ * Each constructor has two variants, one accepts a <code>String</code> node
+ * URI, the other accepts a <code>String[]</code> node path. The former is
+ * used by the DmtAdmin implementation, the latter by the plugins, who receive
+ * the node URI as an array of segment names. The constructors are otherwise
+ * identical.
+ * <p>
+ * Getter methods are provided to retrieve the values of the additional
+ * parameters, and the <code>printStackTrace(PrintWriter)</code> method is
+ * extended to print the stack trace of all causing throwables as well.
+ */
+public class DmtException extends Exception {
+ private static final long serialVersionUID = -63006267148118655L;
+
+ // ----- Public constants -----//
+
+ /**
+ * The originator's authentication credentials specify a principal with
+ * insufficient rights to complete the command.
+ * <p>
+ * This status code is used as response to device originated sessions if the
+ * remote management server cannot authorize the device to perform the
+ * requested operation.
+ * <p>
+ * This error code corresponds to the OMA DM response status code 401
+ * "Unauthorized".
+ */
+ public static final int UNAUTHORIZED = 401;
+
+ /**
+ * The requested target node was not found. No indication is given as to
+ * whether this is a temporary or permanent condition, unless otherwise
+ * noted.
+ * <p>
+ * This is only used when the requested node name is valid, otherwise the
+ * more specific error codes {@link #URI_TOO_LONG} or {@link #INVALID_URI}
+ * are used. This error code corresponds to the OMA DM response status code
+ * 404 "Not Found".
+ */
+ public static final int NODE_NOT_FOUND = 404;
+
+ /**
+ * The requested command is not allowed on the target node. This includes
+ * the following situations:
+ * <ul>
+ * <li>an interior node operation is requested for a leaf node, or vice
+ * versa (e.g. trying to retrieve the children of a leaf node)
+ * <li>an attempt is made to create a node where the parent is a leaf node
+ * <li>an attempt is made to rename or delete the root node of the tree
+ * <li>an attempt is made to rename or delete the root node of the session
+ * <li>a write operation (other than setting the ACL) is performed in a
+ * non-atomic write session on a node provided by a plugin that is read-only
+ * or does not support non-atomic writing
+ * <li>a node is copied to its descendant
+ * <li>the ACL of the root node is changed not to include Add rights for
+ * all principals
+ * </ul>
+ * <p>
+ * This error code corresponds to the OMA DM response status code 405
+ * "Command not allowed".
+ */
+ public static final int COMMAND_NOT_ALLOWED = 405;
+
+ /**
+ * The requested command failed because an optional feature required by the
+ * command is not supported. For example, opening an atomic session might
+ * return this error code if the DmtAdmin implementation does not support
+ * transactions. Similarly, accessing the optional node properties (Title,
+ * Timestamp, Version, Size) might not succeed if either the DmtAdmin
+ * implementation or the underlying plugin does not support the property.
+ * <p>
+ * When getting or setting values for interior nodes (an optional
+ * optimization feature), a plugin can use this error code to indicate that
+ * the given interior node does not support values.
+ * <p>
+ * This error code corresponds to the OMA DM response status code 406
+ * "Optional feature not supported".
+ */
+ public static final int FEATURE_NOT_SUPPORTED = 406;
+
+ /**
+ * The requested command failed because the target URI or one of its
+ * segments is too long for what the recipient is able or willing to
+ * process, or the target URI contains too many segments. The length and
+ * segment number limits are implementation dependent, their minimum values
+ * can be found in the Non Functional Requirements section of the OSGi
+ * specification.
+ * <p>
+ * The {@link Uri#mangle(String)} method provides support for ensuring that
+ * a URI segment conforms to the length limits set by the implementation.
+ * <p>
+ * This error code corresponds to the OMA DM response status code 414
+ * "URI too long".
+ *
+ * @see "OSGi Service Platform, Mobile Specification Release 4"
+ */
+ public static final int URI_TOO_LONG = 414;
+
+ /**
+ * The requested node creation operation failed because the target already
+ * exists. This can occur if the node is created directly (with one of the
+ * <code>create...</code> methods), or indirectly (during a
+ * <code>copy</code> operation).
+ * <p>
+ * This error code corresponds to the OMA DM response status code 418
+ * "Already exists".
+ */
+ public static final int NODE_ALREADY_EXISTS = 418;
+
+ /**
+ * The requested command failed because the principal associated with the
+ * session does not have adequate access control permissions (ACL) on the
+ * target. This can only appear in case of remote sessions, i.e. if the
+ * session is associated with an authenticated principal.
+ * <p>
+ * This error code corresponds to the OMA DM response status code 425
+ * "Permission denied".
+ */
+ public static final int PERMISSION_DENIED = 425;
+
+ /**
+ * The recipient encountered an error which prevented it from fulfilling the
+ * request.
+ * <p>
+ * This error code is only used in situations not covered by any of the
+ * other error codes that a method may use. Some methods specify more
+ * specific error situations for this code, but it can generally be used for
+ * any unexpected condition that causes the command to fail.
+ * <p>
+ * This error code corresponds to the OMA DM response status code 500
+ * "Command Failed".
+ */
+ public static final int COMMAND_FAILED = 500;
+
+ /**
+ * An error related to the recipient data store occurred while processing
+ * the request. This error code may be thrown by any of the methods
+ * accessing the tree, but whether it is really used depends on the
+ * implementation, and the data store it uses.
+ * <p>
+ * This error code corresponds to the OMA DM response status code 510
+ * "Data store failure".
+ */
+ public static final int DATA_STORE_FAILURE = 510;
+
+ /**
+ * The rollback command was not completed successfully. The tree might be in
+ * an inconsistent state after this error.
+ * <p>
+ * This error code corresponds to the OMA DM response status code 516
+ * "Atomic roll back failed".
+ */
+ public static final int ROLLBACK_FAILED = 516;
+
+
+ /**
+ * A device initiated remote operation failed. This is used when the
+ * protocol adapter fails to send an alert for any reason.
+ * <p>
+ * Alert routing errors (that occur while looking for the proper protocol
+ * adapter to use) are indicated by {@link #ALERT_NOT_ROUTED}, this code is
+ * only for errors encountered while sending the routed alert. This error
+ * code does not correspond to any OMA DM response status code. It should be
+ * translated to the code 500 "Command Failed" when transferring
+ * over OMA DM.
+ */
+ public static final int REMOTE_ERROR = 1;
+
+ /**
+ * Operation failed because of meta data restrictions. This covers any
+ * attempted deviation from the parameters defined by the
+ * <code>MetaNode</code> objects of the affected nodes, for example in the
+ * following situations:
+ * <ul>
+ * <li>creating, deleting or renaming a permanent node, or modifying its
+ * type or value
+ * <li>creating an interior node where the meta-node defines it as a leaf,
+ * or vice versa
+ * <li>any operation on a node which does not have the required access type
+ * (e.g. executing a node that lacks the <code>MetaNode.CMD_EXECUTE</code>
+ * access type)
+ * <li>any node creation or deletion that would violate the cardinality
+ * constraints
+ * <li>any leaf node value setting that would violate the allowed formats,
+ * values, mime types, etc.
+ * <li>any node creation that would violate the allowed node names
+ * </ul>
+ * <p>
+ * This error code can also be used to indicate any other meta data
+ * violation, even if it cannot be described by the <code>MetaNode</code>
+ * class. For example, detecting a multi-node constraint violation while
+ * committing an atomic session should result in this error.
+ * <p>
+ * This error code does not correspond to any OMA DM response status code.
+ * It should be translated to the code 405 "Command not allowed"
+ * when transferring over OMA DM.
+ */
+ public static final int METADATA_MISMATCH = 2;
+
+ /**
+ * The requested command failed because the target URI or node name is
+ * <code>null</code> or syntactically invalid. This covers the following
+ * cases:
+ * <ul>
+ * <li>the URI or node name ends with the '\' or '/' character
+ * <li>the URI is an empty string (only invalid if the method does not
+ * accept relative URIs)
+ * <li>the URI contains the segment "<code>.</code>" at a position
+ * other than the beginning of the URI
+ * <li>the node name is "<code>..</code>" or the URI contains such
+ * a segment
+ * <li>the node name is an empty string or the URI contains an empty segment
+ * <li>the node name contains an unescaped '/' character
+ * </ul>
+ * <p>
+ * See the {@link Uri#mangle(String)} method for support on escaping invalid
+ * characters in a URI.
+ * <p>
+ * This code is only used if the URI or node name does not match any of the
+ * criteria for {@link #URI_TOO_LONG}. This error code does not correspond
+ * to any OMA DM response status code. It should be translated to the code
+ * 404 "Not Found" when transferring over OMA DM.
+ */
+ public static final int INVALID_URI = 3;
+
+ /**
+ * An error occurred related to concurrent access of nodes. This can happen
+ * for example if a configuration node was deleted directly through the
+ * Configuration Admin service, while the node was manipulated via the tree.
+ * <p>
+ * This error code does not correspond to any OMA DM response status code.
+ * It should be translated to the code 500 "Command Failed" when
+ * transferring over OMA DM.
+ */
+ public static final int CONCURRENT_ACCESS = 4;
+
+ /**
+ * An alert can not be sent from the device to the given principal. This can
+ * happen if there is no Remote Alert Sender willing to forward the alert to
+ * the given principal, or if no principal was given and the DmtAdmin did
+ * not find an appropriate default destination.
+ * <p>
+ * This error code does not correspond to any OMA DM response status code.
+ * It should be translated to the code 500 "Command Failed" when
+ * transferring over OMA DM.
+ */
+ public static final int ALERT_NOT_ROUTED = 5;
+
+ /**
+ * A transaction-related error occurred in an atomic session. This error is
+ * caused by one of the following situations:
+ * <ul>
+ * <li>an updating method within an atomic session can not be executed
+ * because the underlying plugin is read-only or does not support atomic
+ * writing</li>
+ * <li>a commit operation at the end of an atomic session failed because
+ * one of the underlying plugins failed to close</li>
+ * </ul>
+ * The latter case may leave the tree in an inconsistent state due to the
+ * lack of a two-phase commit system, see {@link DmtSession#commit} for
+ * details.
+ * <p>
+ * This error code does not correspond to any OMA DM response status code.
+ * It should be translated to the code 500 "Command Failed" when
+ * transferring over OMA DM.
+ */
+ public static final int TRANSACTION_ERROR = 6;
+
+ /**
+ * Creation of a session timed out because of another ongoing session. The
+ * length of time while the DmtAdmin waits for the blocking session(s) to
+ * finish is implementation dependant.
+ * <p>
+ * This error code does not correspond to any OMA DM response status code.
+ * OMA has several status codes related to timeout, but these are meant to
+ * be used when a request times out, not if a session can not be
+ * established. This error code should be translated to the code 500
+ * "Command Failed" when transferring over OMA DM.
+ */
+ public static final int SESSION_CREATION_TIMEOUT = 7;
+
+ // ----- Content fields -----//
+
+ /**
+ * The URI of the node on which the failed DMT operation was issued, or
+ * <code>null</code> if the operation was not associated with a node.
+ */
+ private final String uri;
+
+ /**
+ * The error code of the failure, one of the constants defined in this
+ * class.
+ */
+ private final int code;
+
+ /**
+ * The message associated with the exception, or <code>null</code> if
+ * there is no error message.
+ */
+ private final String message;
+
+ /**
+ * The list of originating exceptions, or empty list or <code>null</code>
+ * if there are no originating exceptions.
+ */
+ private final Throwable[] causes;
+
+ /**
+ * Determines whether the exception is fatal or not. This is basically a
+ * two-state severity indicator, with the 'fatal' severity being the more
+ * serious one.
+ */
+ private final boolean fatal;
+
+ // ----- Constructors -----//
+
+ /**
+ * Create an instance of the exception. The <code>uri</code> and
+ * <code>message</code> parameters are optional. No originating exception
+ * is specified.
+ *
+ * @param uri the node on which the failed DMT operation was issued, or
+ * <code>null</code> if the operation is not associated with a node
+ * @param code the error code of the failure
+ * @param message the message associated with the exception, or
+ * <code>null</code> if there is no error message
+ */
+ public DmtException(String uri, int code, String message) {
+ this(uri, code, message, new Throwable[0], false);
+ }
+
+ /**
+ * Create an instance of the exception, specifying the cause exception. The
+ * <code>uri</code>, <code>message</code> and <code>cause</code>
+ * parameters are optional.
+ *
+ * @param uri the node on which the failed DMT operation was issued, or
+ * <code>null</code> if the operation is not associated with a node
+ * @param code the error code of the failure
+ * @param message the message associated with the exception, or
+ * <code>null</code> if there is no error message
+ * @param cause the originating exception, or <code>null</code> if there
+ * is no originating exception
+ */
+ public DmtException(String uri, int code, String message, Throwable cause) {
+ this(uri, code, message, (cause == null) ? new Throwable[0]
+ : new Throwable[] { cause }, false);
+ }
+
+ /**
+ * Create an instance of the exception, specifying the list of cause
+ * exceptions and whether the exception is a fatal one. This constructor is
+ * meant to be used by plugins wishing to indicate that a serious error
+ * occurred which should invalidate the ongoing atomic session. The
+ * <code>uri</code>, <code>message</code> and <code>causes</code>
+ * parameters are optional.
+ * <p>
+ * If a fatal exception is thrown, no further business methods will be
+ * called on the originator plugin. In case of atomic sessions, all other
+ * open plugins will be rolled back automatically, except if the fatal
+ * exception was thrown during commit.
+ *
+ * @param uri the node on which the failed DMT operation was issued, or
+ * <code>null</code> if the operation is not associated with a node
+ * @param code the error code of the failure
+ * @param message the message associated with the exception, or
+ * <code>null</code> if there is no error message
+ * @param causes the list of originating exceptions, or empty list or
+ * <code>null</code> if there are no originating exceptions
+ * @param fatal whether the exception is fatal
+ */
+ public DmtException(String uri, int code, String message, Vector causes,
+ boolean fatal) {
+ this(uri, code, message, (causes == null) ? new Throwable[0]
+ : (Throwable[]) causes.toArray(new Throwable[causes.size()]),
+ fatal);
+ }
+
+ private DmtException(String uri, int code, String message,
+ Throwable[] causes, boolean fatal) {
+ this.uri = uri;
+ this.code = code;
+ this.message = message;
+ this.causes = causes;
+ this.fatal = fatal;
+ }
+
+ /**
+ * Create an instance of the exception, specifying the target node as an
+ * array of path segments. This method behaves in exactly the same way as if
+ * the path was given as a URI string.
+ *
+ * @param path the path of the node on which the failed DMT operation was
+ * issued, or <code>null</code> if the operation is not associated
+ * with a node
+ * @param code the error code of the failure
+ * @param message the message associated with the exception, or
+ * <code>null</code> if there is no error message
+ * @see #DmtException(String, int, String)
+ */
+ public DmtException(String[] path, int code, String message) {
+ this(pathToUri(path), code, message);
+ }
+
+ /**
+ * Create an instance of the exception, specifying the target node as an
+ * array of path segments, and specifying the cause exception. This method
+ * behaves in exactly the same way as if the path was given as a URI string.
+ *
+ * @param path the path of the node on which the failed DMT operation was
+ * issued, or <code>null</code> if the operation is not associated
+ * with a node
+ * @param code the error code of the failure
+ * @param message the message associated with the exception, or
+ * <code>null</code> if there is no error message
+ * @param cause the originating exception, or <code>null</code> if there
+ * is no originating exception
+ * @see #DmtException(String, int, String, Throwable)
+ */
+ public DmtException(String[] path, int code, String message, Throwable cause) {
+ this(pathToUri(path), code, message, cause);
+ }
+
+ /**
+ * Create an instance of the exception, specifying the target node as an
+ * array of path segments, the list of cause exceptions, and whether the
+ * exception is a fatal one. This method behaves in exactly the same way as
+ * if the path was given as a URI string.
+ *
+ * @param path the path of the node on which the failed DMT operation was
+ * issued, or <code>null</code> if the operation is not associated
+ * with a node
+ * @param code the error code of the failure
+ * @param message the message associated with the exception, or
+ * <code>null</code> if there is no error message
+ * @param causes the list of originating exceptions, or empty list or
+ * <code>null</code> if there are no originating exceptions
+ * @param fatal whether the exception is fatal
+ * @see #DmtException(String, int, String, Vector, boolean)
+ */
+ public DmtException(String[] path, int code, String message, Vector causes,
+ boolean fatal) {
+ this(pathToUri(path), code, message, causes, fatal);
+ }
+
+ // ----- Public methods -----//
+
+ /**
+ * Get the node on which the failed DMT operation was issued. Some
+ * operations like <code>DmtSession.close()</code> don't require an URI,
+ * in this case this method returns <code>null</code>.
+ *
+ * @return the URI of the node, or <code>null</code>
+ */
+ public String getURI() {
+ return uri;
+ }
+
+ /**
+ * Get the error code associated with this exception. Most of the error
+ * codes within this exception correspond to OMA DM error codes.
+ *
+ * @return the error code
+ */
+ public int getCode() {
+ return code;
+ }
+
+ /**
+ * Get the message associated with this exception. The returned string also
+ * contains the associated URI (if any) and the exception code. The
+ * resulting message has the following format (parts in square brackets are
+ * only included if the field inside them is not <code>null</code>):
+ *
+ * <pre>
+ * <exception_code>[: '<uri>'][: <error_message>]
+ * </pre>
+ *
+ * @return the error message in the format described above
+ */
+ public String getMessage() {
+ StringBuffer sb = new StringBuffer(getCodeText(code));
+ if (uri != null)
+ sb.append(": '").append(uri).append('\'');
+ if (message != null)
+ sb.append(": ").append(message);
+
+ return sb.toString();
+ }
+
+ /**
+ * Get the cause of this exception. Returns non-<code>null</code>, if
+ * this exception is caused by one or more other exceptions (like a
+ * <code>NullPointerException</code> in a DmtPlugin). If there are more
+ * than one cause exceptions, the first one is returned.
+ *
+ * @return the cause of this exception, or <code>null</code> if no cause
+ * was given
+ */
+ public Throwable getCause() {
+ return causes.length == 0 ? null : causes[0];
+ }
+
+ /**
+ * Get all causes of this exception. Returns the causing exceptions in an
+ * array. If no cause was specified, an empty array is returned.
+ *
+ * @return the list of causes of this exception
+ */
+ public Throwable[] getCauses() {
+ return (Throwable[]) causes.clone();
+ }
+
+ /**
+ * Check whether this exception is marked as fatal in the session. Fatal
+ * exceptions trigger an automatic rollback of atomic sessions.
+ *
+ * @return whether the exception is marked as fatal
+ */
+ public boolean isFatal() {
+ return fatal;
+ }
+
+ /**
+ * Prints the exception and its backtrace to the specified print stream. Any
+ * causes that were specified for this exception are also printed, together
+ * with their backtraces.
+ *
+ * @param s <code>PrintStream</code> to use for output
+ */
+ public void printStackTrace(PrintStream s) {
+ super.printStackTrace(s);
+ for (int i = 0; i<causes.length; i++) {
+ s.print("Caused by" + (i > 0 ? " (" + (i+1) + ")" : "") + ": ");
+ causes[i].printStackTrace(s);
+ }
+ }
+
+ // ----- Utility methods -----//
+
+ /**
+ * Converts the given path, given as an array of path segments, to a single
+ * URI string.
+ *
+ * @param path the path to convert
+ * @return the URI string representing the same node as the given path
+ */
+ static String pathToUri(String[] path) {
+ if (path == null)
+ return null;
+
+ return Uri.toUri(path);
+ }
+
+ /**
+ * Returns the name of the given error code.
+ *
+ * @param code the error code
+ * @return a string containing the error code name
+ */
+ private static String getCodeText(int code) {
+ // todo sync codes
+ switch (code) {
+ case NODE_NOT_FOUND:
+ return "NODE_NOT_FOUND";
+ case COMMAND_NOT_ALLOWED:
+ return "COMMAND_NOT_ALLOWED";
+ case FEATURE_NOT_SUPPORTED:
+ return "FEATURE_NOT_SUPPORTED";
+ case URI_TOO_LONG:
+ return "URI_TOO_LONG";
+ case NODE_ALREADY_EXISTS:
+ return "NODE_ALREADY_EXISTS";
+ case PERMISSION_DENIED:
+ return "PERMISSION_DENIED";
+ case COMMAND_FAILED:
+ return "COMMAND_FAILED";
+ case DATA_STORE_FAILURE:
+ return "DATA_STORE_FAILURE";
+ case ROLLBACK_FAILED:
+ return "ROLLBACK_FAILED";
+
+ case REMOTE_ERROR:
+ return "REMOTE_ERROR";
+ case METADATA_MISMATCH:
+ return "METADATA_MISMATCH";
+ case INVALID_URI:
+ return "INVALID_URI";
+ case CONCURRENT_ACCESS:
+ return "CONCURRENT_ACCESS";
+ case ALERT_NOT_ROUTED:
+ return "ALERT_NOT_ROUTED";
+ case TRANSACTION_ERROR:
+ return "TRANSACTION_ERROR";
+ case SESSION_CREATION_TIMEOUT:
+ return "SESSION_CREATION_TIMEOUT";
+ default:
+ return "<unknown code>";
+ }
+ }
+}
diff --git a/org.osgi.compendium/src/main/java/info/dmtree/DmtIllegalStateException.java b/org.osgi.compendium/src/main/java/info/dmtree/DmtIllegalStateException.java
new file mode 100644
index 0000000..e5c06b2
--- /dev/null
+++ b/org.osgi.compendium/src/main/java/info/dmtree/DmtIllegalStateException.java
@@ -0,0 +1,84 @@
+/*
+ * $Header: /cvshome/build/info.dmtree/src/info/dmtree/DmtIllegalStateException.java,v 1.4 2006/07/13 13:42:12 tszeredi Exp $
+ *
+ * Copyright (c) OSGi Alliance (2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package info.dmtree;
+
+/**
+ * Unchecked illegal state exception. This class is used in DMT because
+ * java.lang.IllegalStateException does not exist in CLDC.
+ */
+public class DmtIllegalStateException extends RuntimeException {
+ private static final long serialVersionUID = 2015244852018469700L;
+
+ /**
+ * Nested exception.
+ */
+ private final Throwable cause;
+
+ /**
+ * Create an instance of the exception with no message.
+ */
+ public DmtIllegalStateException() {
+ super();
+ cause = null;
+ }
+
+ /**
+ * Create an instance of the exception with the specified message.
+ *
+ * @param message the reason for the exception
+ */
+ public DmtIllegalStateException(String message) {
+ super(message);
+ cause = null;
+ }
+
+ /**
+ * Create an instance of the exception with the specified cause exception
+ * and no message.
+ *
+ * @param cause the cause of the exception
+ */
+ public DmtIllegalStateException(Throwable cause) {
+ super();
+ this.cause = cause;
+ }
+
+ /**
+ * Create an instance of the exception with the specified message and cause
+ * exception.
+ *
+ * @param message the reason for the exception
+ * @param cause the cause of the exception
+ */
+ public DmtIllegalStateException(String message, Throwable cause) {
+ super(message);
+ this.cause = cause;
+ }
+
+ /**
+ * Returns the cause of this exception or <code>null</code> if no cause
+ * was specified when this exception was created.
+ *
+ * @return the cause of this exception or <code>null</code> if no cause
+ * was specified
+ */
+ public Throwable getCause() {
+ return cause;
+ }
+}
diff --git a/org.osgi.compendium/src/main/java/info/dmtree/DmtSession.java b/org.osgi.compendium/src/main/java/info/dmtree/DmtSession.java
new file mode 100644
index 0000000..dd1913a
--- /dev/null
+++ b/org.osgi.compendium/src/main/java/info/dmtree/DmtSession.java
@@ -0,0 +1,1599 @@
+/*
+ * $Header: /cvshome/build/info.dmtree/src/info/dmtree/DmtSession.java,v 1.7 2006/07/11 16:58:20 tszeredi Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package info.dmtree;
+
+import java.util.Date;
+
+/**
+ * DmtSession provides concurrent access to the DMT. All DMT manipulation
+ * commands for management applications are available on the
+ * <code>DmtSession</code> interface. The session is associated with a root
+ * node which limits the subtree in which the operations can be executed within
+ * this session.
+ * <p>
+ * Most of the operations take a node URI as parameter, which can be either an
+ * absolute URI (starting with "./") or a URI relative to the root
+ * node of the session. The empty string as relative URI means the root URI the
+ * session was opened with. All segments of a URI must be within the segment
+ * length limit of the implementation, and the special characters '/' and '\'
+ * must be escaped (preceded by a '\'). Any string can be converted to a valid
+ * URI segment using the {@link Uri#mangle(String)} method.
+ * <p>
+ * If the URI specified does not correspond to a legitimate node in the tree an
+ * exception is thrown. The only exception is the {@link #isNodeUri(String)}
+ * method which returns <code>false</code> in case of an invalid URI.
+ * <p>
+ * Each method of <code>DmtSession</code> that accesses the tree in any way
+ * can throw <code>DmtIllegalStateException</code> if the session has been
+ * closed or invalidated (due to timeout, fatal exceptions, or unexpectedly
+ * unregistered plugins).
+ */
+public interface DmtSession {
+ /**
+ * Sessions created with <code>LOCK_TYPE_SHARED</code> lock allows
+ * read-only access to the tree, but can be shared between multiple readers.
+ */
+ int LOCK_TYPE_SHARED = 0;
+
+ /**
+ * <code>LOCK_TYPE_EXCLUSIVE</code> lock guarantees full access to the
+ * tree, but can not be shared with any other locks.
+ */
+ int LOCK_TYPE_EXCLUSIVE = 1;
+
+ /**
+ * <code>LOCK_TYPE_ATOMIC</code> is an exclusive lock with transactional
+ * functionality. Commands of an atomic session will either fail or succeed
+ * together, if a single command fails then the whole session will be rolled
+ * back.
+ */
+ int LOCK_TYPE_ATOMIC = 2;
+
+ /**
+ * The session is open, all session operations are available.
+ */
+ int STATE_OPEN = 0;
+
+ /**
+ * The session is closed, DMT manipulation operations are not available,
+ * they throw <code>DmtIllegalStateException</code> if tried.
+ */
+ int STATE_CLOSED = 1;
+
+ /**
+ * The session is invalid because a fatal error happened. Fatal errors
+ * include the timeout of the session, any DmtException with the 'fatal'
+ * flag set, or the case when a plugin service is unregistered while in use
+ * by the session. DMT manipulation operations are not available, they throw
+ * <code>DmtIllegalStateException</code> if tried.
+ */
+ int STATE_INVALID = 2;
+
+ /**
+ * Get the current state of this session.
+ *
+ * @return the state of the session, one of {@link #STATE_OPEN},
+ * {@link #STATE_CLOSED} and {@link #STATE_INVALID}
+ */
+ int getState();
+
+ /**
+ * Gives the type of lock the session has.
+ *
+ * @return the lock type of the session, one of {@link #LOCK_TYPE_SHARED},
+ * {@link #LOCK_TYPE_EXCLUSIVE} and {@link #LOCK_TYPE_ATOMIC}
+ */
+ int getLockType();
+
+ /**
+ * Gives the name of the principal on whose behalf the session was created.
+ * Local sessions do not have an associated principal, in this case
+ * <code>null</code> is returned.
+ *
+ * @return the identifier of the remote server that initiated the session,
+ * or <code>null</code> for local sessions
+ */
+ String getPrincipal();
+
+ /**
+ * The unique identifier of the session. The ID is generated automatically,
+ * and it is guaranteed to be unique on a machine.
+ *
+ * @return the session identification number
+ */
+ int getSessionId();
+
+ /**
+ * Get the root URI associated with this session. Gives "<code>.</code>"
+ * if the session was created without specifying a root, which means that
+ * the target of this session is the whole DMT.
+ *
+ * @return the root URI
+ */
+ String getRootUri();
+
+ /**
+ * Commits a series of DMT operations issued in the current atomic session
+ * since the last transaction boundary. Transaction boundaries are the
+ * creation of this object that starts the session, and all subsequent
+ * {@link #commit} and {@link #rollback} calls.
+ * <p>
+ * This method can fail even if all operations were successful. This can
+ * happen due to some multi-node semantic constraints defined by a specific
+ * implementation. For example, node A can be required to always have
+ * children A/B, A/C and A/D. If this condition is broken when
+ * <code>commit()</code> is executed, the method will fail, and throw a
+ * <code>METADATA_MISMATCH</code> exception.
+ * <p>
+ * An error situation can arise due to the lack of a two phase commit
+ * mechanism in the underlying plugins. As an example, if plugin A has
+ * committed successfully but plugin B failed, the whole session must fail,
+ * but there is no way to undo the commit performed by A. To provide
+ * predictable behaviour, the commit operation should continue with the
+ * remaining plugins even after detecting a failure. All exceptions received
+ * from failed commits are aggregated into one
+ * <code>TRANSACTION_ERROR</code> exception thrown by this method.
+ * <p>
+ * In many cases the tree is not the only way to manage a given part of the
+ * system. It may happen that while modifying some nodes in an atomic
+ * session, the underlying settings are modified in parallel outside the
+ * scope of the DMT. If this is detected during commit, an exception with
+ * the code <code>CONCURRENT_ACCESS</code> is thrown.
+ *
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>METADATA_MISMATCH</code> if the operation failed
+ * because of meta-data restrictions
+ * <li><code>CONCURRENT_ACCESS</code> if it is detected that some
+ * modification has been made outside the scope of the DMT to the
+ * nodes affected in the session's operations
+ * <li><code>TRANSACTION_ERROR</code> if an error occurred during
+ * the commit of any of the underlying plugins
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws DmtIllegalStateException if the session was not opened using the
+ * <code>LOCK_TYPE_ATOMIC</code> lock type, or if the session is
+ * already closed or invalidated
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation
+ */
+ void commit() throws DmtException;
+
+ /**
+ * Rolls back a series of DMT operations issued in the current atomic
+ * session since the last transaction boundary. Transaction boundaries are
+ * the creation of this object that starts the session, and all subsequent
+ * {@link #commit} and {@link #rollback} calls.
+ *
+ * @throws DmtException with the error code <code>ROLLBACK_FAILED</code>
+ * in case the rollback did not succeed
+ * @throws DmtIllegalStateException if the session was not opened using the
+ * <code>LOCK_TYPE_ATOMIC</code> lock type, or if the session is
+ * already closed or invalidated
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation
+ */
+ void rollback() throws DmtException;
+
+ /**
+ * Closes a session. If the session was opened with atomic lock mode, the
+ * <code>DmtSession</code> must first persist the changes made to the DMT
+ * by calling <code>commit()</code> on all (transactional) plugins
+ * participating in the session. See the documentation of the
+ * {@link #commit} method for details and possible errors during this
+ * operation.
+ * <p>
+ * The state of the session changes to <code>DmtSession.STATE_CLOSED</code>
+ * if the close operation completed successfully, otherwise it becomes
+ * <code>DmtSession.STATE_INVALID</code>.
+ *
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>METADATA_MISMATCH</code> in case of atomic sessions,
+ * if the commit operation failed because of meta-data restrictions
+ * <li><code>CONCURRENT_ACCESS</code> in case of atomic sessions,
+ * if the commit operation failed because of some modification
+ * outside the scope of the DMT to the nodes affected in the session
+ * <li><code>TRANSACTION_ERROR</code> in case of atomic sessions,
+ * if an underlying plugin failed to commit
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if an underlying plugin failed
+ * to close, or if some unspecified error is encountered while
+ * attempting to complete the command
+ * </ul>
+ * @throws DmtIllegalStateException if the session is already closed or
+ * invalidated
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation
+ */
+ void close() throws DmtException;
+
+ /**
+ * Executes a node. This corresponds to the EXEC operation in OMA DM. This
+ * method cannot be called in a read-only session.
+ * <p>
+ * The semantics of an execute operation and the data parameter it takes
+ * depends on the definition of the managed object on which the command is
+ * issued.
+ *
+ * @param nodeUri the node on which the execute operation is issued
+ * @param data the parameter of the execute operation, can be
+ * <code>null</code>
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>URI_TOO_LONG</code> if <code>nodeUri</code> or a
+ * segment of it is too long, or if it has too many segments
+ * <li><code>INVALID_URI</code> if <code>nodeUri</code> is
+ * <code>null</code> or syntactically invalid
+ * <li><code>NODE_NOT_FOUND</code> if the node does not exist and
+ * the plugin does not allow executing unexisting nodes
+ * <li><code>PERMISSION_DENIED</code> if the session is
+ * associated with a principal and the ACL of the node does not
+ * allow the <code>Execute</code> operation for the associated
+ * principal
+ * <li><code>METADATA_MISMATCH</code> if the node cannot be
+ * executed according to the meta-data (does not have
+ * <code>MetaNode.CMD_EXECUTE</code> access type)
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if the URI is not within the
+ * current session's subtree, if no DmtExecPlugin is associated with
+ * the node and the DmtAdmin can not execute the node, or if some
+ * unspecified error is encountered while attempting to complete the
+ * command
+ * </ul>
+ * @throws DmtIllegalStateException if the session was opened using the
+ * <code>LOCK_TYPE_SHARED</code> lock type, or if the session is
+ * already closed or invalidated
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation, or,
+ * in case of local sessions, if the caller does not have
+ * <code>DmtPermission</code> for the node with the Exec action
+ * present
+ *
+ * @see #execute(String, String, String)
+ */
+ void execute(String nodeUri, String data) throws DmtException;
+
+ /**
+ * Executes a node, also specifying a correlation ID for use in response
+ * notifications. This operation corresponds to the EXEC command in OMA DM.
+ * This method cannot be called in a read-only session.
+ * <p>
+ * The semantics of an execute operation and the data parameter it takes
+ * depends on the definition of the managed object on which the command is
+ * issued. If a correlation ID is specified, it should be used as the
+ * <code>correlator</code> parameter for notifications sent in response to this
+ * execute operation.
+ *
+ * @param nodeUri the node on which the execute operation is issued
+ * @param correlator an identifier to associate this operation with any
+ * notifications sent in response to it, can be <code>null</code> if not
+ * needed
+ * @param data the parameter of the execute operation, can be
+ * <code>null</code>
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>URI_TOO_LONG</code> if <code>nodeUri</code> or a
+ * segment of it is too long, or if it has too many segments
+ * <li><code>INVALID_URI</code> if <code>nodeUri</code> is
+ * <code>null</code> or syntactically invalid
+ * <li><code>NODE_NOT_FOUND</code> if the node does not exist and
+ * the plugin does not allow executing unexisting nodes
+ * <li><code>PERMISSION_DENIED</code> if the session is
+ * associated with a principal and the ACL of the node does not
+ * allow the <code>Execute</code> operation for the associated
+ * principal
+ * <li><code>METADATA_MISMATCH</code> if the node cannot be
+ * executed according to the meta-data (does not have
+ * <code>MetaNode.CMD_EXECUTE</code> access type)
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if the URI is not within the
+ * current session's subtree, if no DmtExecPlugin is associated with
+ * the node, or if some unspecified error is encountered while
+ * attempting to complete the command
+ * </ul>
+ * @throws DmtIllegalStateException if the session was opened using the
+ * <code>LOCK_TYPE_SHARED</code> lock type, or if the session is
+ * already closed or invalidated
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation, or,
+ * in case of local sessions, if the caller does not have
+ * <code>DmtPermission</code> for the node with the Exec action
+ * present
+ * @see #execute(String, String)
+ */
+ void execute(String nodeUri, String correlator, String data)
+ throws DmtException;
+
+ /**
+ * Get the Access Control List associated with a given node. The returned
+ * <code>Acl</code> object does not take inheritance into account, it
+ * gives the ACL specifically given to the node.
+ *
+ * @param nodeUri the URI of the node
+ * @return the Access Control List belonging to the node or
+ * <code>null</code> if none defined
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>URI_TOO_LONG</code> if <code>nodeUri</code> or a
+ * segment of it is too long, or if it has too many segments
+ * <li><code>INVALID_URI</code> if <code>nodeUri</code> is
+ * <code>null</code> or syntactically invalid
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodeUri</code>
+ * points to a non-existing node
+ * <li><code>PERMISSION_DENIED</code> if the session is
+ * associated with a principal and the ACL of the node does not
+ * allow the <code>Get</code> operation for the associated
+ * principal
+ * <li><code>METADATA_MISMATCH</code> if node information cannot
+ * be retrieved according to the meta-data (the node does not have
+ * <code>MetaNode.CMD_GET</code> access type)
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if the URI is not within the
+ * current session's subtree, or if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws DmtIllegalStateException if the session is already closed or
+ * invalidated
+ * @throws SecurityException in case of local sessions, if the caller does
+ * not have <code>DmtPermission</code> for the node with the Get
+ * action present
+ * @see #getEffectiveNodeAcl
+ */
+ Acl getNodeAcl(String nodeUri) throws DmtException;
+
+ /**
+ * Gives the Access Control List in effect for a given node. The returned
+ * <code>Acl</code> takes inheritance into account, that is if there is no
+ * ACL defined for the node, it will be derived from the closest ancestor
+ * having an ACL defined.
+ *
+ * @param nodeUri the URI of the node
+ * @return the Access Control List belonging to the node
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>URI_TOO_LONG</code> if <code>nodeUri</code> or a
+ * segment of it is too long, or if it has too many segments
+ * <li><code>INVALID_URI</code> if <code>nodeUri</code> is
+ * <code>null</code> or syntactically invalid
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodeUri</code>
+ * points to a non-existing node
+ * <li><code>PERMISSION_DENIED</code> if the session is
+ * associated with a principal and the ACL of the node does not
+ * allow the <code>Get</code> operation for the associated
+ * principal
+ * <li><code>METADATA_MISMATCH</code> if node information cannot
+ * be retrieved according to the meta-data (the node does not have
+ * <code>MetaNode.CMD_GET</code> access type)
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if the URI is not within the
+ * current session's subtree, or if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws DmtIllegalStateException if the session is already closed or
+ * invalidated
+ * @throws SecurityException in case of local sessions, if the caller does
+ * not have <code>DmtPermission</code> for the node with the Get
+ * action present
+ * @see #getNodeAcl
+ */
+ Acl getEffectiveNodeAcl(String nodeUri) throws DmtException;
+
+ /**
+ * Set the Access Control List associated with a given node. To perform this
+ * operation, the caller needs to have replace rights (<code>Acl.REPLACE</code>
+ * or the corresponding Java permission depending on the session type) as
+ * described below:
+ * <ul>
+ * <li>if <code>nodeUri</code> specifies a leaf node, replace rights are
+ * needed on the parent of the node
+ * <li>if <code>nodeUri</code> specifies an interior node, replace rights
+ * on either the node or its parent are sufficient
+ * </ul>
+ * <p>
+ * If the given <code>acl</code> is <code>null</code> or an empty ACL
+ * (not specifying any permissions for any principals), then the ACL of the
+ * node is deleted, and the node will inherit the ACL from its parent node.
+ *
+ * @param nodeUri the URI of the node
+ * @param acl the Access Control List to be set on the node, can be
+ * <code>null</code>
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>URI_TOO_LONG</code> if <code>nodeUri</code> or a
+ * segment of it is too long, or if it has too many segments
+ * <li><code>INVALID_URI</code> if <code>nodeUri</code> is
+ * <code>null</code> or syntactically invalid
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodeUri</code>
+ * points to a non-existing node
+ * <li><code>PERMISSION_DENIED</code> if the session is
+ * associated with a principal and the ACL of the node or its parent
+ * (see above) does not allow the <code>Replace</code> operation
+ * for the associated principal
+ * <li><code>COMMAND_NOT_ALLOWED</code> if the command attempts
+ * to set the ACL of the root node not to include Add rights for all
+ * principals
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if the URI is not within the
+ * current session's subtree, or if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws DmtIllegalStateException if the session was opened using the
+ * <code>LOCK_TYPE_SHARED</code> lock type, or if the session is
+ * already closed or invalidated
+ * @throws SecurityException in case of local sessions, if the caller does
+ * not have <code>DmtPermission</code> for the node or its parent
+ * (see above) with the Replace action present
+ */
+ void setNodeAcl(String nodeUri, Acl acl) throws DmtException;
+
+ /**
+ * Create a copy of a node or a whole subtree. Beside the structure and
+ * values of the nodes, most properties are also copied, with the exception
+ * of the ACL (Access Control List), Timestamp and Version properties.
+ * <p>
+ * The copy method is essentially a convenience method that could be
+ * substituted with a sequence of retrieval and update operations. This
+ * determines the permissions required for copying. However, some
+ * optimization can be possible if the source and target nodes are all
+ * handled by DmtAdmin or by the same plugin. In this case, the handler
+ * might be able to perform the underlying management operation more
+ * efficiently: for example, a configuration table can be copied at once
+ * instead of reading each node for each entry and creating it in the new
+ * tree.
+ * <p>
+ * This method may result in any of the errors possible for the contributing
+ * operations. Most of these are collected in the exception descriptions
+ * below, but for the full list also consult the documentation of
+ * {@link #getChildNodeNames(String)}, {@link #isLeafNode(String)},
+ * {@link #getNodeValue(String)}, {@link #getNodeType(String)},
+ * {@link #getNodeTitle(String)}, {@link #setNodeTitle(String, String)},
+ * {@link #createLeafNode(String, DmtData, String)} and
+ * {@link #createInteriorNode(String, String)}.
+ *
+ * @param nodeUri the node or root of a subtree to be copied
+ * @param newNodeUri the URI of the new node or root of a subtree
+ * @param recursive <code>false</code> if only a single node is copied,
+ * <code>true</code> if the whole subtree is copied
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>URI_TOO_LONG</code> if <code>nodeUri</code> or
+ * <code>newNodeUri</code> or any segment of them is too long, or
+ * if they have too many segments
+ * <li><code>INVALID_URI</code> if <code>nodeUri</code> or
+ * <code>newNodeUri</code> is <code>null</code> or syntactically
+ * invalid
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodeUri</code>
+ * points to a non-existing node, or if <code>newNodeUri</code>
+ * points to a node that cannot exist in the tree according to the
+ * meta-data (see {@link #getMetaNode(String)})
+ * <li><code>NODE_ALREADY_EXISTS</code> if
+ * <code>newNodeUri</code> points to a node that already exists
+ * <li><code>PERMISSION_DENIED</code> if the session is
+ * associated with a principal and the ACL of the copied node(s)
+ * does not allow the <code>Get</code> operation, or the ACL of
+ * the parent of the target node does not allow the <code>Add</code>
+ * operation for the associated principal
+ * <li><code>COMMAND_NOT_ALLOWED</code> if <code>nodeUri</code>
+ * is an ancestor of <code>newNodeUri</code>, or if any of the
+ * implied retrieval or update operations are not allowed
+ * <li><code>METADATA_MISMATCH</code> if any of the meta-data
+ * constraints of the implied retrieval or update operations are
+ * violated
+ * <li><code>TRANSACTION_ERROR</code> in an atomic session if the
+ * underlying plugin is read-only or does not support atomic writing
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if either URI is not within
+ * the current session's subtree, or if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws DmtIllegalStateException if the session was opened using the
+ * <code>LOCK_TYPE_SHARED</code> lock type, or if the session is
+ * already closed or invalidated
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation, or,
+ * in case of local sessions, if the caller does not have
+ * <code>DmtPermission</code> for the copied node(s) with the Get
+ * action present, or for the parent of the target node with the Add
+ * action
+ */
+ void copy(String nodeUri, String newNodeUri, boolean recursive)
+ throws DmtException;
+
+ /**
+ * Create an interior node. If the parent node does not exist, it is created
+ * automatically, as if this method were called for the parent URI. This way
+ * all missing ancestor nodes leading to the specified node are created. Any
+ * exceptions encountered while creating the ancestors are propagated to the
+ * caller of this method, these are not explicitly listed in the error
+ * descriptions below.
+ * <p>
+ * If meta-data is available for the node, several checks are made before
+ * creating it. The node must have <code>MetaNode.CMD_ADD</code> access
+ * type, it must be defined as a non-permanent interior node, the node name
+ * must conform to the valid names, and the creation of the new node must
+ * not cause the maximum occurrence number to be exceeded.
+ * <p>
+ * If the meta-data cannot be retrieved because the given node cannot
+ * possibly exist in the tree (it is not defined in the specification), the
+ * <code>NODE_NOT_FOUND</code> error code is returned (see
+ * {@link #getMetaNode(String)}).
+ *
+ * @param nodeUri the URI of the node to create
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>URI_TOO_LONG</code> if <code>nodeUri</code> or a
+ * segment of it is too long, or if it has too many segments
+ * <li><code>INVALID_URI</code> if <code>nodeUri</code> is
+ * <code>null</code> or syntactically invalid
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodeUri</code>
+ * points to a node that cannot exist in the tree (see above)
+ * <li><code>NODE_ALREADY_EXISTS</code> if <code>nodeUri</code>
+ * points to a node that already exists
+ * <li><code>PERMISSION_DENIED</code> if the session is
+ * associated with a principal and the ACL of the parent node does
+ * not allow the <code>Add</code> operation for the associated
+ * principal
+ * <li><code>COMMAND_NOT_ALLOWED</code> if the parent node is not
+ * an interior node, or in non-atomic sessions if the underlying
+ * plugin is read-only or does not support non-atomic writing
+ * <li><code>METADATA_MISMATCH</code> if the node could not be
+ * created because of meta-data restrictions (see above)
+ * <li><code>TRANSACTION_ERROR</code> in an atomic session if the
+ * underlying plugin is read-only or does not support atomic writing
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if the URI is not within the
+ * current session's subtree, or if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws DmtIllegalStateException if the session was opened using the
+ * <code>LOCK_TYPE_SHARED</code> lock type, or if the session is
+ * already closed or invalidated
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation, or,
+ * in case of local sessions, if the caller does not have
+ * <code>DmtPermission</code> for the parent node with the Add
+ * action present
+ */
+ void createInteriorNode(String nodeUri) throws DmtException;
+
+ /**
+ * Create an interior node with a given type. The type of interior node, if
+ * specified, is a URI identifying a DDF document. If the parent node does
+ * not exist, it is created automatically, as if
+ * {@link #createInteriorNode(String)} were called for the parent URI. This
+ * way all missing ancestor nodes leading to the specified node are created.
+ * Any exceptions encountered while creating the ancestors are propagated to
+ * the caller of this method, these are not explicitly listed in the error
+ * descriptions below.
+ * <p>
+ * If meta-data is available for the node, several checks are made before
+ * creating it. The node must have <code>MetaNode.CMD_ADD</code> access
+ * type, it must be defined as a non-permanent interior node, the node name
+ * must conform to the valid names, and the creation of the new node must
+ * not cause the maximum occurrence number to be exceeded.
+ * <p>
+ * If the meta-data cannot be retrieved because the given node cannot
+ * possibly exist in the tree (it is not defined in the specification), the
+ * <code>NODE_NOT_FOUND</code> error code is returned (see
+ * {@link #getMetaNode(String)}).
+ * <p>
+ * Interior node type identifiers must follow the format defined in section
+ * 7.7.7.2 of the OMA Device Management Tree and Description document.
+ * Checking the validity of the type string does not have to be done by the
+ * DmtAdmin, this can be left to the plugin handling the node (if any), to
+ * avoid unnecessary double-checks.
+ *
+ * @param nodeUri the URI of the node to create
+ * @param type the type URI of the interior node, can be <code>null</code>
+ * if no node type is defined
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>URI_TOO_LONG</code> if <code>nodeUri</code> or a
+ * segment of it is too long, or if it has too many segments
+ * <li><code>INVALID_URI</code> if <code>nodeUri</code> is
+ * <code>null</code> or syntactically invalid
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodeUri</code>
+ * points to a node that cannot exist in the tree (see above)
+ * <li><code>NODE_ALREADY_EXISTS</code> if <code>nodeUri</code>
+ * points to a node that already exists
+ * <li><code>PERMISSION_DENIED</code> if the session is
+ * associated with a principal and the ACL of the parent node does
+ * not allow the <code>Add</code> operation for the associated
+ * principal
+ * <li><code>COMMAND_NOT_ALLOWED</code> if the parent node is not
+ * an interior node, or in non-atomic sessions if the underlying
+ * plugin is read-only or does not support non-atomic writing
+ * <li><code>METADATA_MISMATCH</code> if the node could not be
+ * created because of meta-data restrictions (see above)
+ * <li><code>TRANSACTION_ERROR</code> in an atomic session if the
+ * underlying plugin is read-only or does not support atomic writing
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if the URI is not within the
+ * current session's subtree, if the type string is invalid (see
+ * above), or if some unspecified error is encountered while
+ * attempting to complete the command
+ * </ul>
+ * @throws DmtIllegalStateException if the session was opened using the
+ * <code>LOCK_TYPE_SHARED</code> lock type, or if the session is
+ * already closed or invalidated
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation, or,
+ * in case of local sessions, if the caller does not have
+ * <code>DmtPermission</code> for the parent node with the Add
+ * action present
+ * @see #createInteriorNode(String)
+ * @see <a
+ * href="http://member.openmobilealliance.org/ftp/public_documents/dm/Permanent_documents/OMA-TS-DM-TND-V1_2-20050615-C.zip">
+ * OMA Device Management Tree and Description v1.2 draft</a>
+ */
+ void createInteriorNode(String nodeUri, String type) throws DmtException;
+
+ /**
+ * Create a leaf node with default value and MIME type. If a node does not
+ * have a default value or MIME type, this method will throw a
+ * <code>DmtException</code> with error code
+ * <code>METADATA_MISMATCH</code>. Note that a node might have a default
+ * value or MIME type even if there is no meta-data for the node or its
+ * meta-data does not specify the default.
+ * <p>
+ * If the parent node does not exist, it is created automatically, as if
+ * {@link #createInteriorNode(String)} were called for the parent URI. This
+ * way all missing ancestor nodes leading to the specified node are created.
+ * Any exceptions encountered while creating the ancestors are propagated to
+ * the caller of this method, these are not explicitly listed in the error
+ * descriptions below.
+ * <p>
+ * If meta-data is available for a node, several checks are made before
+ * creating it. The node must have <code>MetaNode.CMD_ADD</code> access
+ * type, it must be defined as a non-permanent leaf node, the node name must
+ * conform to the valid names, and the creation of the new node must not
+ * cause the maximum occurrence number to be exceeded.
+ * <p>
+ * If the meta-data cannot be retrieved because the given node cannot
+ * possibly exist in the tree (it is not defined in the specification), the
+ * <code>NODE_NOT_FOUND</code> error code is returned (see
+ * {@link #getMetaNode(String)}).
+ *
+ * @param nodeUri the URI of the node to create
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>URI_TOO_LONG</code> if <code>nodeUri</code> or a
+ * segment of it is too long, or if it has too many segments
+ * <li><code>INVALID_URI</code> if <code>nodeUri</code> is
+ * <code>null</code> or syntactically invalid
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodeUri</code>
+ * points to a node that cannot exist in the tree (see above)
+ * <li><code>NODE_ALREADY_EXISTS</code> if <code>nodeUri</code>
+ * points to a node that already exists
+ * <li><code>PERMISSION_DENIED</code> if the session is
+ * associated with a principal and the ACL of the parent node does
+ * not allow the <code>Add</code> operation for the associated
+ * principal
+ * <li><code>COMMAND_NOT_ALLOWED</code> if the parent node is not
+ * an interior node, or in non-atomic sessions if the underlying
+ * plugin is read-only or does not support non-atomic writing
+ * <li><code>METADATA_MISMATCH</code> if the node could not be
+ * created because of meta-data restrictions (see above)
+ * <li><code>TRANSACTION_ERROR</code> in an atomic session if the
+ * underlying plugin is read-only or does not support atomic writing
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if the URI is not within the
+ * current session's subtree, or if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws DmtIllegalStateException if the session was opened using the
+ * <code>LOCK_TYPE_SHARED</code> lock type, or if the session is
+ * already closed or invalidated
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation, or,
+ * in case of local sessions, if the caller does not have
+ * <code>DmtPermission</code> for the parent node with the Add
+ * action present
+ * @see #createLeafNode(String, DmtData)
+ */
+ void createLeafNode(String nodeUri) throws DmtException;
+
+ /**
+ * Create a leaf node with a given value and the default MIME type. If the
+ * specified value is <code>null</code>, the default value is taken. If
+ * the node does not have a default MIME type or value (if needed), this
+ * method will throw a <code>DmtException</code> with error code
+ * <code>METADATA_MISMATCH</code>. Note that a node might have a default
+ * value or MIME type even if there is no meta-data for the node or its
+ * meta-data does not specify the default.
+ * <p>
+ * If the parent node does not exist, it is created automatically, as if
+ * {@link #createInteriorNode(String)} were called for the parent URI. This
+ * way all missing ancestor nodes leading to the specified node are created.
+ * Any exceptions encountered while creating the ancestors are propagated to
+ * the caller of this method, these are not explicitly listed in the error
+ * descriptions below.
+ * <p>
+ * If meta-data is available for a node, several checks are made before
+ * creating it. The node must have <code>MetaNode.CMD_ADD</code> access
+ * type, it must be defined as a non-permanent leaf node, the node name must
+ * conform to the valid names, the node value must conform to the value
+ * constraints, and the creation of the new node must not cause the maximum
+ * occurrence number to be exceeded.
+ * <p>
+ * If the meta-data cannot be retrieved because the given node cannot
+ * possibly exist in the tree (it is not defined in the specification), the
+ * <code>NODE_NOT_FOUND</code> error code is returned (see
+ * {@link #getMetaNode(String)}).
+ * <p>
+ * Nodes of <code>null</code> format can be created by using
+ * {@link DmtData#NULL_VALUE} as second argument.
+ *
+ * @param nodeUri the URI of the node to create
+ * @param value the value to be given to the new node, can be
+ * <code>null</code>
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>URI_TOO_LONG</code> if <code>nodeUri</code> or a
+ * segment of it is too long, or if it has too many segments
+ * <li><code>INVALID_URI</code> if <code>nodeUri</code> is
+ * <code>null</code> or syntactically invalid
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodeUri</code>
+ * points to a node that cannot exist in the tree (see above)
+ * <li><code>NODE_ALREADY_EXISTS</code> if <code>nodeUri</code>
+ * points to a node that already exists
+ * <li><code>PERMISSION_DENIED</code> if the session is
+ * associated with a principal and the ACL of the parent node does
+ * not allow the <code>Add</code> operation for the associated
+ * principal
+ * <li><code>COMMAND_NOT_ALLOWED</code> if the parent node is not
+ * an interior node, or in non-atomic sessions if the underlying
+ * plugin is read-only or does not support non-atomic writing
+ * <li><code>METADATA_MISMATCH</code> if the node could not be
+ * created because of meta-data restrictions (see above)
+ * <li><code>TRANSACTION_ERROR</code> in an atomic session if the
+ * underlying plugin is read-only or does not support atomic writing
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if the URI is not within the
+ * current session's subtree, or if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws DmtIllegalStateException if the session was opened using the
+ * <code>LOCK_TYPE_SHARED</code> lock type, or if the session is
+ * already closed or invalidated
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation, or,
+ * in case of local sessions, if the caller does not have
+ * <code>DmtPermission</code> for the parent node with the Add
+ * action present
+ */
+ void createLeafNode(String nodeUri, DmtData value) throws DmtException;
+
+ /**
+ * Create a leaf node with a given value and MIME type. If the specified
+ * value or MIME type is <code>null</code>, their default values are
+ * taken. If the node does not have the necessary defaults, this method will
+ * throw a <code>DmtException</code> with error code
+ * <code>METADATA_MISMATCH</code>. Note that a node might have a default
+ * value or MIME type even if there is no meta-data for the node or its
+ * meta-data does not specify the default.
+ * <p>
+ * If the parent node does not exist, it is created automatically, as if
+ * {@link #createInteriorNode(String)} were called for the parent URI. This
+ * way all missing ancestor nodes leading to the specified node are created.
+ * Any exceptions encountered while creating the ancestors are propagated to
+ * the caller of this method, these are not explicitly listed in the error
+ * descriptions below.
+ * <p>
+ * If meta-data is available for a node, several checks are made before
+ * creating it. The node must have <code>MetaNode.CMD_ADD</code> access
+ * type, it must be defined as a non-permanent leaf node, the node name must
+ * conform to the valid names, the node value must conform to the value
+ * constraints, the MIME type must be among the listed types, and the
+ * creation of the new node must not cause the maximum occurrence number to
+ * be exceeded.
+ * <p>
+ * If the meta-data cannot be retrieved because the given node cannot
+ * possibly exist in the tree (it is not defined in the specification), the
+ * <code>NODE_NOT_FOUND</code> error code is returned (see
+ * {@link #getMetaNode(String)}).
+ * <p>
+ * Nodes of <code>null</code> format can be created by using
+ * {@link DmtData#NULL_VALUE} as second argument.
+ * <p>
+ * The MIME type string must conform to the definition in RFC 2045. Checking
+ * its validity does not have to be done by the DmtAdmin, this can be left
+ * to the plugin handling the node (if any), to avoid unnecessary
+ * double-checks.
+ *
+ * @param nodeUri the URI of the node to create
+ * @param value the value to be given to the new node, can be
+ * <code>null</code>
+ * @param mimeType the MIME type to be given to the new node, can be
+ * <code>null</code>
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>URI_TOO_LONG</code> if <code>nodeUri</code> or a
+ * segment of it is too long, or if it has too many segments
+ * <li><code>INVALID_URI</code> if <code>nodeUri</code> is
+ * <code>null</code> or syntactically invalid
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodeUri</code>
+ * points to a node that cannot exist in the tree (see above)
+ * <li><code>NODE_ALREADY_EXISTS</code> if <code>nodeUri</code>
+ * points to a node that already exists
+ * <li><code>PERMISSION_DENIED</code> if the session is
+ * associated with a principal and the ACL of the parent node does
+ * not allow the <code>Add</code> operation for the associated
+ * principal
+ * <li><code>COMMAND_NOT_ALLOWED</code> if the parent node is not
+ * an interior node, or in non-atomic sessions if the underlying
+ * plugin is read-only or does not support non-atomic writing
+ * <li><code>METADATA_MISMATCH</code> if the node could not be
+ * created because of meta-data restrictions (see above)
+ * <li><code>TRANSACTION_ERROR</code> in an atomic session if the
+ * underlying plugin is read-only or does not support atomic writing
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if the URI is not within the
+ * current session's subtree, if <code>mimeType</code> is not a
+ * proper MIME type string (see above), or if some unspecified error
+ * is encountered while attempting to complete the command
+ * </ul>
+ * @throws DmtIllegalStateException if the session was opened using the
+ * <code>LOCK_TYPE_SHARED</code> lock type, or if the session is
+ * already closed or invalidated
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation, or,
+ * in case of local sessions, if the caller does not have
+ * <code>DmtPermission</code> for the parent node with the Add
+ * action present
+ * @see #createLeafNode(String, DmtData)
+ * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>
+ */
+ void createLeafNode(String nodeUri, DmtData value, String mimeType)
+ throws DmtException;
+
+ /**
+ * Delete the given node. Deleting interior nodes is recursive, the whole
+ * subtree under the given node is deleted. It is not allowed to delete
+ * the root node of the session.
+ * <p>
+ * If meta-data is available for a node, several checks are made before
+ * deleting it. The node must be non-permanent, it must have the
+ * <code>MetaNode.CMD_DELETE</code> access type, and if zero occurrences
+ * of the node are not allowed, it must not be the last one.
+ *
+ * @param nodeUri the URI of the node
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>URI_TOO_LONG</code> if <code>nodeUri</code> or a
+ * segment of it is too long, or if it has too many segments
+ * <li><code>INVALID_URI</code> if <code>nodeUri</code> is
+ * <code>null</code> or syntactically invalid
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodeUri</code>
+ * points to a non-existing node
+ * <li><code>PERMISSION_DENIED</code> if the session is
+ * associated with a principal and the ACL of the node does not
+ * allow the <code>Delete</code> operation for the associated
+ * principal
+ * <li><code>COMMAND_NOT_ALLOWED</code> if the target node is the
+ * root of the session, or in non-atomic sessions if the underlying
+ * plugin is read-only or does not support non-atomic writing
+ * <li><code>METADATA_MISMATCH</code> if the node could not be
+ * deleted because of meta-data restrictions (see above)
+ * <li><code>TRANSACTION_ERROR</code> in an atomic session if the
+ * underlying plugin is read-only or does not support atomic writing
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if the URI is not within the
+ * current session's subtree, or if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws DmtIllegalStateException if the session was opened using the
+ * <code>LOCK_TYPE_SHARED</code> lock type, or if the session is
+ * already closed or invalidated
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation, or,
+ * in case of local sessions, if the caller does not have
+ * <code>DmtPermission</code> for the node with the Delete action
+ * present
+ */
+ void deleteNode(String nodeUri) throws DmtException;
+
+ /**
+ * Rename a node. This operation only changes the name of the node (updating
+ * the timestamp and version properties if they are supported), the value
+ * and the other properties are not changed. The new name of the node must
+ * be provided, the new URI is constructed from the base of the old URI and
+ * the given name. It is not allowed to rename the root node of the session.
+ * <p>
+ * If available, the meta-data of the original and the new nodes are checked
+ * before performing the rename operation. Neither node can be permanent,
+ * their leaf/interior property must match, and the name change must not
+ * violate any of the cardinality constraints. The original node must have
+ * the <code>MetaNode.CMD_REPLACE</code> access type, and the name of the
+ * new node must conform to the valid names.
+ *
+ * @param nodeUri the URI of the node to rename
+ * @param newName the new name property of the node
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>URI_TOO_LONG</code> if <code>nodeUri</code> or a
+ * segment of it is too long, if <code>nodeUri</code> has too many
+ * segments, or if <code>newName</code> is too long
+ * <li><code>INVALID_URI</code> if <code>nodeUri</code> or
+ * <code>newName</code> is <code>null</code> or syntactically
+ * invalid
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodeUri</code>
+ * points to a non-existing node, or if the new node is not defined
+ * in the tree according to the meta-data (see
+ * {@link #getMetaNode(String)})
+ * <li><code>NODE_ALREADY_EXISTS</code> if there already exists a
+ * sibling of <code>nodeUri</code> with the name
+ * <code>newName</code>
+ * <li><code>PERMISSION_DENIED</code> if the session is
+ * associated with a principal and the ACL of the node does not
+ * allow the <code>Replace</code> operation for the associated
+ * principal
+ * <li><code>COMMAND_NOT_ALLOWED</code> if the target node is the
+ * root of the session, or in non-atomic sessions if the underlying
+ * plugin is read-only or does not support non-atomic writing
+ * <li><code>METADATA_MISMATCH</code> if the node could not be
+ * renamed because of meta-data restrictions (see above)
+ * <li><code>TRANSACTION_ERROR</code> in an atomic session if the
+ * underlying plugin is read-only or does not support atomic writing
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if the URI is not within the
+ * current session's subtree, or if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws DmtIllegalStateException if the session was opened using the
+ * <code>LOCK_TYPE_SHARED</code> lock type, or if the session is
+ * already closed or invalidated
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation, or,
+ * in case of local sessions, if the caller does not have
+ * <code>DmtPermission</code> for the node with the Replace action
+ * present
+ */
+ void renameNode(String nodeUri, String newName) throws DmtException;
+
+ /**
+ * Set the value of a leaf or interior node to its default. The default
+ * can be defined by the node's <code>MetaNode</code>. The method throws a
+ * <code>METADATA_MISMATCH</code> exception if the node does not have a
+ * default value.
+ *
+ * @param nodeUri the URI of the node
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>URI_TOO_LONG</code> if <code>nodeUri</code> or a
+ * segment of it is too long, or if it has too many segments
+ * <li><code>INVALID_URI</code> if <code>nodeUri</code> is
+ * <code>null</code> or syntactically invalid
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodeUri</code>
+ * points to a non-existing node
+ * <li><code>PERMISSION_DENIED</code> if the session is
+ * associated with a principal and the ACL of the node does not
+ * allow the <code>Replace</code> operation for the associated
+ * principal
+ * <li><code>COMMAND_NOT_ALLOWED</code> in non-atomic sessions if
+ * the underlying plugin is read-only or does not support non-atomic
+ * writing
+ * <li><code>METADATA_MISMATCH</code> if the node is permanent or
+ * cannot be modified according to the meta-data (does not have the
+ * <code>MetaNode.CMD_REPLACE</code> access type), or if there is
+ * no default value defined for this node
+ * <li><code>FEATURE_NOT_SUPPORTED</code> if the specified node is
+ * an interior node and does not support Java object values
+ * <li><code>TRANSACTION_ERROR</code> in an atomic session if the
+ * underlying plugin is read-only or does not support atomic writing
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if the URI is not within the
+ * current session's subtree, or if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws DmtIllegalStateException if the session was opened using the
+ * <code>LOCK_TYPE_SHARED</code> lock type, or if the session is
+ * already closed or invalidated
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation, or,
+ * in case of local sessions, if the caller does not have
+ * <code>DmtPermission</code> for the node with the Replace action
+ * present
+ * @see #setNodeValue
+ */
+ void setDefaultNodeValue(String nodeUri) throws DmtException;
+
+ /**
+ * Set the value of a leaf or interior node. The format of the node is
+ * contained in the <code>DmtData</code> object. For interior nodes, the
+ * format must be <code>FORMAT_NODE</code>, while for leaf nodes this
+ * format must not be used.
+ * <p>
+ * If the specified value is <code>null</code>, the default value is taken.
+ * In this case, if the node does not have a default value, this method will
+ * throw a <code>DmtException</code> with error code
+ * <code>METADATA_MISMATCH</code>. Nodes of <code>null</code> format can be
+ * set by using {@link DmtData#NULL_VALUE} as second argument.
+ * <p>
+ * An Event of type REPLACE is sent out for a leaf node. A replaced interior
+ * node sends out events for each of its children in depth first order
+ * and node names sorted with Arrays.sort(String[]). When setting a value
+ * on an interior node, the values of the leaf nodes under it can change,
+ * but the structure of the subtree is not modified by the operation.
+ *
+ * @param nodeUri the URI of the node
+ * @param data the data to be set, can be <code>null</code>
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>URI_TOO_LONG</code> if <code>nodeUri</code> or a
+ * segment of it is too long, or if it has too many segments
+ * <li><code>INVALID_URI</code> if <code>nodeUri</code> is
+ * <code>null</code> or syntactically invalid
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodeUri</code>
+ * points to a non-existing node
+ * <li><code>PERMISSION_DENIED</code> if the session is
+ * associated with a principal and the ACL of the node does not
+ * allow the <code>Replace</code> operation for the associated
+ * principal
+ * <li><code>COMMAND_NOT_ALLOWED</code> if the given data has
+ * <code>FORMAT_NODE</code> format but the node is a leaf node (or
+ * vice versa), or in non-atomic sessions if the underlying plugin
+ * is read-only or does not support non-atomic writing
+ * <li><code>METADATA_MISMATCH</code> if the node is permanent or
+ * cannot be modified according to the meta-data (does not have the
+ * <code>MetaNode.CMD_REPLACE</code> access type), or if the given
+ * value does not conform to the meta-data value constraints
+ * <li><code>FEATURE_NOT_SUPPORTED</code> if the specified node is
+ * an interior node and does not support Java object values
+ * <li><code>TRANSACTION_ERROR</code> in an atomic session if the
+ * underlying plugin is read-only or does not support atomic writing
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if the URI is not within the
+ * current session's subtree, or if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws DmtIllegalStateException if the session was opened using the
+ * <code>LOCK_TYPE_SHARED</code> lock type, or if the session is
+ * already closed or invalidated
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation, or,
+ * in case of local sessions, if the caller does not have
+ * <code>DmtPermission</code> for the node with the Replace action
+ * present
+ */
+ void setNodeValue(String nodeUri, DmtData data) throws DmtException;
+
+ /**
+ * Set the title property of a node. The length of the title string in UTF-8
+ * encoding must not exceed 255 bytes.
+ *
+ * @param nodeUri the URI of the node
+ * @param title the title text of the node, can be <code>null</code>
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>URI_TOO_LONG</code> if <code>nodeUri</code> or a
+ * segment of it is too long, or if it has too many segments
+ * <li><code>INVALID_URI</code> if <code>nodeUri</code> is
+ * <code>null</code> or syntactically invalid
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodeUri</code>
+ * points to a non-existing node
+ * <li><code>PERMISSION_DENIED</code> if the session is
+ * associated with a principal and the ACL of the node does not
+ * allow the <code>Replace</code> operation for the associated
+ * principal
+ * <li><code>COMMAND_NOT_ALLOWED</code> in non-atomic sessions if
+ * the underlying plugin is read-only or does not support non-atomic
+ * writing
+ * <li><code>METADATA_MISMATCH</code> if the node cannot be
+ * modified according to the meta-data (does not have the
+ * <code>MetaNode.CMD_REPLACE</code> access type)
+ * <li><code>FEATURE_NOT_SUPPORTED</code> if the Title property
+ * is not supported by the DmtAdmin implementation or the
+ * underlying plugin
+ * <li><code>TRANSACTION_ERROR</code> in an atomic session if the
+ * underlying plugin is read-only or does not support atomic writing
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if the title string is too
+ * long, if the URI is not within the current session's subtree, or
+ * if some unspecified error is encountered while attempting to
+ * complete the command
+ * </ul>
+ * @throws DmtIllegalStateException if the session was opened using the
+ * <code>LOCK_TYPE_SHARED</code> lock type, or if the session is
+ * already closed or invalidated
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation, or,
+ * in case of local sessions, if the caller does not have
+ * <code>DmtPermission</code> for the node with the Replace action
+ * present
+ */
+ void setNodeTitle(String nodeUri, String title) throws DmtException;
+
+ /**
+ * Set the type of a node. The type of leaf node is the MIME type of the
+ * data it contains. The type of an interior node is a URI identifying a DDF
+ * document.
+ * <p>
+ * For interior nodes, a <code>null</code> type string means that there is
+ * no DDF document overriding the tree structure defined by the ancestors.
+ * For leaf nodes, it requests that the default MIME type is used for the
+ * given node. If the node does not have a default MIME type this method
+ * will throw a <code>DmtException</code> with error code
+ * <code>METADATA_MISMATCH</code>. Note that a node might have a default
+ * MIME type even if there is no meta-data for the node or its meta-data
+ * does not specify the default.
+ * <p>
+ * MIME types must conform to the definition in RFC 2045. Interior node type
+ * identifiers must follow the format defined in section 7.7.7.2 of the OMA
+ * Device Management Tree and Description document. Checking the validity of
+ * the type string does not have to be done by the DmtAdmin, this can be
+ * left to the plugin handling the node (if any), to avoid unnecessary
+ * double-checks.
+ *
+ * @param nodeUri the URI of the node
+ * @param type the type of the node, can be <code>null</code>
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>URI_TOO_LONG</code> if <code>nodeUri</code> or a
+ * segment of it is too long, or if it has too many segments
+ * <li><code>INVALID_URI</code> if <code>nodeUri</code> is
+ * <code>null</code> or syntactically invalid
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodeUri</code>
+ * points to a non-existing node
+ * <li><code>PERMISSION_DENIED</code> if the session is
+ * associated with a principal and the ACL of the node does not
+ * allow the <code>Replace</code> operation for the associated
+ * principal
+ * <li><code>COMMAND_NOT_ALLOWED</code> in non-atomic sessions if
+ * the underlying plugin is read-only or does not support non-atomic
+ * writing
+ * <li><code>METADATA_MISMATCH</code> if the node is permanent or
+ * cannot be modified according to the meta-data (does not have the
+ * <code>MetaNode.CMD_REPLACE</code> access type), and in case of
+ * leaf nodes, if <code>null</code> is given and there is no
+ * default MIME type, or the given MIME type is not allowed
+ * <li><code>TRANSACTION_ERROR</code> in an atomic session if the
+ * underlying plugin is read-only or does not support atomic writing
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if the URI is not within the
+ * current session's subtree, if the type string is invalid (see
+ * above), or if some unspecified error is encountered while
+ * attempting to complete the command
+ * </ul>
+ * @throws DmtIllegalStateException if the session was opened using the
+ * <code>LOCK_TYPE_SHARED</code> lock type, or if the session is
+ * already closed or invalidated
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation, or,
+ * in case of local sessions, if the caller does not have
+ * <code>DmtPermission</code> for the node with the Replace action
+ * present
+ * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>
+ * @see <a
+ * href="http://member.openmobilealliance.org/ftp/public_documents/dm/Permanent_documents/OMA-TS-DM-TND-V1_2-20050615-C.zip">
+ * OMA Device Management Tree and Description v1.2 draft</a>
+ */
+ void setNodeType(String nodeUri, String type) throws DmtException;
+
+ /**
+ * Get the list of children names of a node. The returned array contains the
+ * names - not the URIs - of the immediate children nodes of the given node.
+ * The returned child names are mangled ({@link Uri#mangle}). The elements
+ * are in no particular order. The returned array must not contain
+ * <code>null</code> entries.
+ *
+ * @param nodeUri the URI of the node
+ * @return the list of child node names as a string array or an empty string
+ * array if the node has no children
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>URI_TOO_LONG</code> if <code>nodeUri</code> or a
+ * segment of it is too long, or if it has too many segments
+ * <li><code>INVALID_URI</code> if <code>nodeUri</code> is
+ * <code>null</code> or syntactically invalid
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodeUri</code>
+ * points to a non-existing node
+ * <li><code>PERMISSION_DENIED</code> if the session is
+ * associated with a principal and the ACL of the node does not
+ * allow the <code>Get</code> operation for the associated
+ * principal
+ * <li><code>COMMAND_NOT_ALLOWED</code> if the specified node is
+ * not an interior node
+ * <li><code>METADATA_MISMATCH</code> if node information cannot
+ * be retrieved according to the meta-data (it does not have
+ * <code>MetaNode.CMD_GET</code> access type)
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if the URI is not within the
+ * current session's subtree, or if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws DmtIllegalStateException if the session is already closed or
+ * invalidated
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation, or,
+ * in case of local sessions, if the caller does not have
+ * <code>DmtPermission</code> for the node with the Get action
+ * present
+ */
+ String[] getChildNodeNames(String nodeUri) throws DmtException;
+
+ /**
+ * Get the meta data which describes a given node. Meta data can only be
+ * inspected, it can not be changed.
+ * <p>
+ * The <code>MetaNode</code> object returned to the client is the
+ * combination of the meta data returned by the data plugin (if any) plus
+ * the meta data returned by the DmtAdmin. If there are differences in the
+ * meta data elements known by the plugin and the DmtAdmin then the plugin
+ * specific elements take precedence.
+ * <p>
+ * Note, that a node does not have to exist for having meta-data associated
+ * with it. This method may provide meta-data for any node that can possibly
+ * exist in the tree (any node defined in the specification). For nodes that
+ * are not defined, it may throw <code>DmtException</code> with the error
+ * code <code>NODE_NOT_FOUND</code>. To allow easier implementation of
+ * plugins that do not provide meta-data, it is allowed to return
+ * <code>null</code> for any node, regardless of whether it is defined or
+ * not.
+ *
+ * @param nodeUri the URI of the node
+ * @return a MetaNode which describes meta data information, can be
+ * <code>null</code> if there is no meta data available for the
+ * given node
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>URI_TOO_LONG</code> if <code>nodeUri</code> or a
+ * segment of it is too long, or if it has too many segments
+ * <li><code>INVALID_URI</code> if <code>nodeUri</code> is
+ * <code>null</code> or syntactically invalid
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodeUri</code>
+ * points to a node that is not defined in the tree (see above)
+ * <li><code>PERMISSION_DENIED</code> if the session is
+ * associated with a principal and the ACL of the node does not
+ * allow the <code>Get</code> operation for the associated
+ * principal
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if the URI is not within the
+ * current session's subtree, or if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws DmtIllegalStateException if the session is already closed or
+ * invalidated
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation, or,
+ * in case of local sessions, if the caller does not have
+ * <code>DmtPermission</code> for the node with the Get action
+ * present
+ */
+ MetaNode getMetaNode(String nodeUri) throws DmtException;
+
+ /**
+ * Get the size of the data in a leaf node. The returned value depends on
+ * the format of the data in the node, see the description of the
+ * {@link DmtData#getSize()} method for the definition of node size for each
+ * format.
+ *
+ * @param nodeUri the URI of the leaf node
+ * @return the size of the data in the node
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>URI_TOO_LONG</code> if <code>nodeUri</code> or a
+ * segment of it is too long, or if it has too many segments
+ * <li><code>INVALID_URI</code> if <code>nodeUri</code> is
+ * <code>null</code> or syntactically invalid
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodeUri</code>
+ * points to a non-existing node
+ * <li><code>PERMISSION_DENIED</code> if the session is
+ * associated with a principal and the ACL of the node does not
+ * allow the <code>Get</code> operation for the associated
+ * principal
+ * <li><code>COMMAND_NOT_ALLOWED</code> if the specified node is
+ * not a leaf node
+ * <li><code>METADATA_MISMATCH</code> if node information cannot
+ * be retrieved according to the meta-data (it does not have
+ * <code>MetaNode.CMD_GET</code> access type)
+ * <li><code>FEATURE_NOT_SUPPORTED</code> if the Size property is
+ * not supported by the DmtAdmin implementation or the underlying
+ * plugin
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if the URI is not within the
+ * current session's subtree, or if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws DmtIllegalStateException if the session is already closed or
+ * invalidated
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation, or,
+ * in case of local sessions, if the caller does not have
+ * <code>DmtPermission</code> for the node with the Get action
+ * present
+ * @see DmtData#getSize
+ */
+ int getNodeSize(String nodeUri) throws DmtException;
+
+ /**
+ * Get the timestamp when the node was created or last modified.
+ *
+ * @param nodeUri the URI of the node
+ * @return the timestamp of the last modification
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>URI_TOO_LONG</code> if <code>nodeUri</code> or a
+ * segment of it is too long, or if it has too many segments
+ * <li><code>INVALID_URI</code> if <code>nodeUri</code> is
+ * <code>null</code> or syntactically invalid
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodeUri</code>
+ * points to a non-existing node
+ * <li><code>PERMISSION_DENIED</code> if the session is
+ * associated with a principal and the ACL of the node does not
+ * allow the <code>Get</code> operation for the associated
+ * principal
+ * <li><code>METADATA_MISMATCH</code> if node information cannot
+ * be retrieved according to the meta-data (it does not have
+ * <code>MetaNode.CMD_GET</code> access type)
+ * <li><code>FEATURE_NOT_SUPPORTED</code> if the Timestamp
+ * property is not supported by the DmtAdmin implementation or the
+ * underlying plugin
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if the URI is not within the
+ * current session's subtree, or if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws DmtIllegalStateException if the session is already closed or
+ * invalidated
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation, or,
+ * in case of local sessions, if the caller does not have
+ * <code>DmtPermission</code> for the node with the Get action
+ * present
+ */
+ Date getNodeTimestamp(String nodeUri) throws DmtException;
+
+ /**
+ * Get the title of a node. There might be no title property set for a node.
+ *
+ * @param nodeUri the URI of the node
+ * @return the title of the node, or <code>null</code> if the node has no
+ * title
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>URI_TOO_LONG</code> if <code>nodeUri</code> or a
+ * segment of it is too long, or if it has too many segments
+ * <li><code>INVALID_URI</code> if <code>nodeUri</code> is
+ * <code>null</code> or syntactically invalid
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodeUri</code>
+ * points to a non-existing node
+ * <li><code>PERMISSION_DENIED</code> if the session is
+ * associated with a principal and the ACL of the node does not
+ * allow the <code>Get</code> operation for the associated
+ * principal
+ * <li><code>METADATA_MISMATCH</code> if node information cannot
+ * be retrieved according to the meta-data (it does not have
+ * <code>MetaNode.CMD_GET</code> access type)
+ * <li><code>FEATURE_NOT_SUPPORTED</code> if the Title property
+ * is not supported by the DmtAdmin implementation or the
+ * underlying plugin
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if the URI is not within the
+ * current session's subtree, or if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws DmtIllegalStateException if the session is already closed or
+ * invalidated
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation, or,
+ * in case of local sessions, if the caller does not have
+ * <code>DmtPermission</code> for the node with the Get action
+ * present
+ */
+ String getNodeTitle(String nodeUri) throws DmtException;
+
+ /**
+ * Get the type of a node. The type of leaf node is the MIME type of the
+ * data it contains. The type of an interior node is a URI identifying a DDF
+ * document; a <code>null</code> type means that there is no DDF document
+ * overriding the tree structure defined by the ancestors.
+ *
+ * @param nodeUri the URI of the node
+ * @return the type of the node, can be <code>null</code>
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>URI_TOO_LONG</code> if <code>nodeUri</code> or a
+ * segment of it is too long, or if it has too many segments
+ * <li><code>INVALID_URI</code> if <code>nodeUri</code> is
+ * <code>null</code> or syntactically invalid
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodeUri</code>
+ * points to a non-existing node
+ * <li><code>PERMISSION_DENIED</code> if the session is
+ * associated with a principal and the ACL of the node does not
+ * allow the <code>Get</code> operation for the associated
+ * principal
+ * <li><code>METADATA_MISMATCH</code> if node information cannot
+ * be retrieved according to the meta-data (it does not have
+ * <code>MetaNode.CMD_GET</code> access type)
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if the URI is not within the
+ * current session's subtree, or if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws DmtIllegalStateException if the session is already closed or
+ * invalidated
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation, or,
+ * in case of local sessions, if the caller does not have
+ * <code>DmtPermission</code> for the node with the Get action
+ * present
+ */
+ String getNodeType(String nodeUri) throws DmtException;
+
+ /**
+ * Get the data contained in a leaf or interior node. When retrieving the
+ * value associated with an interior node, the caller must have rights to
+ * read all nodes in the subtree under the given node.
+ *
+ * @param nodeUri the URI of the node to retrieve
+ * @return the data of the node, can not be <code>null</code>
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>URI_TOO_LONG</code> if <code>nodeUri</code> or a
+ * segment of it is too long, or if it has too many segments
+ * <li><code>INVALID_URI</code> if <code>nodeUri</code> is
+ * <code>null</code> or syntactically invalid
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodeUri</code>
+ * points to a non-existing node
+ * <li><code>PERMISSION_DENIED</code> if the session is
+ * associated with a principal and the ACL of the node (and the ACLs
+ * of all its descendants in case of interior nodes) do not allow
+ * the <code>Get</code> operation for the associated principal
+ * <li><code>METADATA_MISMATCH</code> if the node value cannot be
+ * retrieved according to the meta-data (it does not have
+ * <code>MetaNode.CMD_GET</code> access type)
+ * <li><code>FEATURE_NOT_SUPPORTED</code> if the specified node is
+ * an interior node and does not support Java object values
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if the URI is not within the
+ * current session's subtree, or if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws DmtIllegalStateException if the session is already closed or
+ * invalidated
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation, or,
+ * in case of local sessions, if the caller does not have
+ * <code>DmtPermission</code> for the node (and all its descendants
+ * in case of interior nodes) with the Get action present
+ */
+ DmtData getNodeValue(String nodeUri) throws DmtException;
+
+ /**
+ * Get the version of a node. The version can not be set, it is calculated
+ * automatically by the device. It is incremented modulo 0x10000 at every
+ * modification of the value or any other property of the node, for both
+ * leaf and interior nodes. When a node is created the initial value is 0.
+ *
+ * @param nodeUri the URI of the node
+ * @return the version of the node
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>URI_TOO_LONG</code> if <code>nodeUri</code> or a
+ * segment of it is too long, or if it has too many segments
+ * <li><code>INVALID_URI</code> if <code>nodeUri</code> is
+ * <code>null</code> or syntactically invalid
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodeUri</code>
+ * points to a non-existing node
+ * <li><code>PERMISSION_DENIED</code> if the session is
+ * associated with a principal and the ACL of the node does not
+ * allow the <code>Get</code> operation for the associated
+ * principal
+ * <li><code>METADATA_MISMATCH</code> if node information cannot
+ * be retrieved according to the meta-data (it does not have
+ * <code>MetaNode.CMD_GET</code> access type)
+ * <li><code>FEATURE_NOT_SUPPORTED</code> if the Version property
+ * is not supported by the DmtAdmin implementation or the
+ * underlying plugin
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if the URI is not within the
+ * current session's subtree, or if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws DmtIllegalStateException if the session is already closed or
+ * invalidated
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation, or,
+ * in case of local sessions, if the caller does not have
+ * <code>DmtPermission</code> for the node with the Get action
+ * present
+ */
+ int getNodeVersion(String nodeUri) throws DmtException;
+
+ /**
+ * Tells whether a node is a leaf or an interior node of the DMT.
+ *
+ * @param nodeUri the URI of the node
+ * @return true if the given node is a leaf node
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>URI_TOO_LONG</code> if <code>nodeUri</code> or a
+ * segment of it is too long, or if it has too many segments
+ * <li><code>INVALID_URI</code> if <code>nodeUri</code> is
+ * <code>null</code> or syntactically invalid
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodeUri</code>
+ * points to a non-existing node
+ * <li><code>PERMISSION_DENIED</code> if the session is
+ * associated with a principal and the ACL of the node does not
+ * allow the <code>Get</code> operation for the associated
+ * principal
+ * <li><code>METADATA_MISMATCH</code> if node information cannot
+ * be retrieved according to the meta-data (it does not have
+ * <code>MetaNode.CMD_GET</code> access type)
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if the URI is not within the
+ * current session's subtree, or if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws DmtIllegalStateException if the session is already closed or
+ * invalidated
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation, or,
+ * in case of local sessions, if the caller does not have
+ * <code>DmtPermission</code> for the node with the Get action
+ * present
+ */
+ boolean isLeafNode(String nodeUri) throws DmtException;
+
+ /**
+ * Check whether the specified URI corresponds to a valid node in the DMT.
+ *
+ * @param nodeUri the URI to check
+ * @return true if the given node exists in the DMT
+ * @throws DmtIllegalStateException if the session is already closed or
+ * invalidated
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation, or,
+ * in case of local sessions, if the caller does not have
+ * <code>DmtPermission</code> for the node with the Get action
+ * present
+ */
+ boolean isNodeUri(String nodeUri);
+}
diff --git a/org.osgi.compendium/src/main/java/info/dmtree/MetaNode.java b/org.osgi.compendium/src/main/java/info/dmtree/MetaNode.java
new file mode 100644
index 0000000..18afcfc
--- /dev/null
+++ b/org.osgi.compendium/src/main/java/info/dmtree/MetaNode.java
@@ -0,0 +1,383 @@
+/*
+ * $Header: /cvshome/build/info.dmtree/src/info/dmtree/MetaNode.java,v 1.4 2006/07/04 12:26:16 tszeredi Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package info.dmtree;
+
+/**
+ * The MetaNode contains meta data as standardized by OMA DM but extends it
+ * (without breaking the compatibility) to provide for better DMT data quality
+ * in an environment where many software components manipulate this data.
+ * <p>
+ * The interface has several types of functions to describe the nodes in the
+ * DMT. Some methods can be used to retrieve standard OMA DM metadata such as
+ * access type, cardinality, default, etc., others are for data extensions such
+ * as valid names and values. In some cases the standard behaviour has been
+ * extended, for example it is possible to provide several valid MIME types, or
+ * to differentiate between normal and automatic dynamic nodes.
+ * <p>
+ * Most methods in this interface receive no input, just return information
+ * about some aspect of the node. However, there are two methods that behave
+ * differently, {@link #isValidName} and {@link #isValidValue}. These
+ * validation methods are given a potential node name or value (respectively),
+ * and can decide whether it is valid for the given node. Passing the validation
+ * methods is a necessary condition for a name or value to be used, but it is
+ * not necessarily sufficient: the plugin may carry out more thorough (more
+ * expensive) checks when the node is actually created or set.
+ * <p>
+ * If a <code>MetaNode</code> is available for a node, the DmtAdmin must use
+ * the information provided by it to filter out invalid requests on that node.
+ * However, not all methods on this interface are actually used for this
+ * purpose, as many of them (e.g. {@link #getFormat} or {@link #getValidNames})
+ * can be substituted with the validating methods. For example,
+ * {@link #isValidValue} can be expected to check the format, minimum, maximum,
+ * etc. of a given value, making it unnecessary for the DmtAdmin to call
+ * {@link #getFormat()}, {@link #getMin()}, {@link #getMax()} etc. separately.
+ * It is indicated in the description of each method if the DmtAdmin does not
+ * enforce the constraints defined by it - such methods are only for external
+ * use, for example in user interfaces.
+ * <p>
+ * Most of the methods of this class return <code>null</code> if a certain
+ * piece of meta information is not defined for the node or providing this
+ * information is not supported. Methods of this class do not throw exceptions.
+ */
+public interface MetaNode {
+
+ /**
+ * Constant for the ADD access type. If {@link #can(int)} returns
+ * <code>true</code> for this operation, this node can potentially be
+ * added to its parent. Nodes with {@link #PERMANENT} or {@link #AUTOMATIC}
+ * scope typically do not have this access type.
+ */
+ int CMD_ADD = 0;
+
+ /**
+ * Constant for the DELETE access type. If {@link #can(int)} returns
+ * <code>true</code> for this operation, the node can potentially be
+ * deleted.
+ */
+ int CMD_DELETE = 1;
+
+ /**
+ * Constant for the EXECUTE access type. If {@link #can(int)} returns
+ * <code>true</code> for this operation, the node can potentially be
+ * executed.
+ */
+ int CMD_EXECUTE = 2;
+
+ /**
+ * Constant for the REPLACE access type. If {@link #can(int)} returns
+ * <code>true</code> for this operation, the value and other properties of
+ * the node can potentially be modified.
+ */
+ int CMD_REPLACE = 3;
+
+ /**
+ * Constant for the GET access type. If {@link #can(int)} returns
+ * <code>true</code> for this operation, the value, the list of child nodes
+ * (in case of interior nodes) and the properties of the node can
+ * potentially be retrieved.
+ */
+ int CMD_GET = 4;
+
+ /**
+ * Constant for representing a permanent node in the tree. This must be
+ * returned by {@link #getScope} if the node cannot be added, deleted or
+ * modified in any way through tree operations. Permanent nodes cannot have
+ * non-permanent nodes as parents.
+ */
+ int PERMANENT = 0;
+
+ /**
+ * Constant for representing a dynamic node in the tree. This must be
+ * returned by {@link #getScope} for all nodes that are not permanent and
+ * are not created automatically by the management object.
+ */
+ int DYNAMIC = 1;
+
+ /**
+ * Constant for representing an automatic node in the tree. This must be
+ * returned by {@link #getScope()} for all nodes that are created
+ * automatically by the management object. Automatic nodes represent a
+ * special case of dynamic nodes, so this scope should be mapped to
+ * {@link #DYNAMIC} when used in an OMA DM context.
+ * <p>
+ * An automatic node is usually created instantly when its parent is
+ * created, but it is also valid if it only appears later, triggered by some
+ * other condition. The exact behaviour must be defined by the Management
+ * Object.
+ */
+ int AUTOMATIC = 2;
+
+ /**
+ * Check whether the given operation is valid for this node. If no meta-data
+ * is provided for a node, all operations are valid.
+ *
+ * @param operation One of the <code>MetaNode.CMD_...</code> constants.
+ * @return <code>false</code> if the operation is not valid for this node
+ * or the operation code is not one of the allowed constants
+ */
+ boolean can(int operation);
+
+ /**
+ * Check whether the node is a leaf node or an internal one.
+ *
+ * @return <code>true</code> if the node is a leaf node
+ */
+ boolean isLeaf();
+
+ /**
+ * Return the scope of the node. Valid values are
+ * {@link #PERMANENT MetaNode.PERMANENT}, {@link #DYNAMIC MetaNode.DYNAMIC}
+ * and {@link #AUTOMATIC MetaNode.AUTOMATIC}. Note that a permanent node is
+ * not the same as a node where the DELETE operation is not allowed.
+ * Permanent nodes never can be deleted, whereas a non-deletable node can
+ * disappear in a recursive DELETE operation issued on one of its parents.
+ * If no meta-data is provided for a node, it can be assumed to be a dynamic
+ * node.
+ *
+ * @return {@link #PERMANENT} for permanent nodes, {@link #AUTOMATIC} for
+ * nodes that are automatically created, and {@link #DYNAMIC}
+ * otherwise
+ */
+ int getScope();
+
+ /**
+ * Get the explanation string associated with this node. Can be
+ * <code>null</code> if no description is provided for this node.
+ *
+ * @return node description string or <code>null</code> for no description
+ */
+ String getDescription();
+
+ /**
+ * Get the number of maximum occurrences of this type of nodes on the same
+ * level in the DMT. Returns <code>Integer.MAX_VALUE</code> if there is no
+ * upper limit. Note that if the occurrence is greater than 1 then this node
+ * can not have siblings with different metadata. In other words, if
+ * different types of nodes coexist on the same level, their occurrence can
+ * not be greater than 1. If no meta-data is provided for a node, there is
+ * no upper limit on the number of occurrences.
+ *
+ * @return The maximum allowed occurrence of this node type
+ */
+ int getMaxOccurrence();
+
+ /**
+ * Check whether zero occurrence of this node is valid. If no meta-data is
+ * returned for a node, zero occurrences are allowed.
+ *
+ * @return <code>true</code> if zero occurrence of this node is valid
+ */
+ boolean isZeroOccurrenceAllowed();
+
+ /**
+ * Get the default value of this node if any.
+ *
+ * @return The default value or <code>null</code> if not defined
+ */
+ DmtData getDefault();
+
+ /**
+ * Get the list of MIME types this node can hold. The first element of the
+ * returned list must be the default MIME type.
+ * <p>
+ * All MIME types are considered valid if no meta-data is provided for a
+ * node or if <code>null</code> is returned by this method. In this case
+ * the default MIME type cannot be retrieved from the meta-data, but the
+ * node may still have a default. This hidden default (if it exists) can be
+ * utilized by passing <code>null</code> as the type parameter of
+ * {@link DmtSession#setNodeType(String, String)} or
+ * {@link DmtSession#createLeafNode(String, DmtData, String)}.
+ *
+ * @return the list of allowed MIME types for this node, starting with the
+ * default MIME type, or <code>null</code> if all types are
+ * allowed
+ */
+ String[] getMimeTypes();
+
+ /**
+ * Get the maximum allowed value associated with a node of numeric format.
+ * If no meta-data is provided for a node, there is no upper limit to its
+ * value. This method is only meaningful if the node has integer or float
+ * format. The returned limit has <code>double</code> type, as this can be
+ * used to denote both integer and float limits with full precision. The
+ * actual maximum should be the largest integer or float number that does
+ * not exceed the returned value.
+ * <p>
+ * The information returned by this method is not checked by DmtAdmin, it
+ * is only for external use, for example in user interfaces. DmtAdmin only
+ * calls {@link #isValidValue} for checking the value, its behaviour should
+ * be consistent with this method.
+ *
+ * @return the allowed maximum, or <code>Double.MAX_VALUE</code> if there
+ * is no upper limit defined or the node's format is not integer or
+ * float
+ */
+ double getMax();
+
+ /**
+ * Get the minimum allowed value associated with a node of numeric format.
+ * If no meta-data is provided for a node, there is no lower limit to its
+ * value. This method is only meaningful if the node has integer or float
+ * format. The returned limit has <code>double</code> type, as this can be
+ * used to denote both integer and float limits with full precision. The
+ * actual minimum should be the smallest integer or float number that is
+ * larger than the returned value.
+ * <p>
+ * The information returned by this method is not checked by DmtAdmin, it
+ * is only for external use, for example in user interfaces. DmtAdmin only
+ * calls {@link #isValidValue} for checking the value, its behaviour should
+ * be consistent with this method.
+ *
+ * @return the allowed minimum, or <code>Double.MIN_VALUE</code> if there
+ * is no lower limit defined or the node's format is not integer or
+ * float
+ */
+ double getMin();
+
+ /**
+ * Return an array of DmtData objects if valid values are defined for the
+ * node, or <code>null</code> otherwise. If no meta-data is provided for a
+ * node, all values are considered valid.
+ * <p>
+ * The information returned by this method is not checked by DmtAdmin, it
+ * is only for external use, for example in user interfaces. DmtAdmin only
+ * calls {@link #isValidValue} for checking the value, its behaviour should
+ * be consistent with this method.
+ *
+ * @return the valid values for this node, or <code>null</code> if not
+ * defined
+ */
+ DmtData[] getValidValues();
+
+ /**
+ * Get the node's format, expressed in terms of type constants defined in
+ * {@link DmtData}. If there are multiple formats allowed for the node then
+ * the format constants are OR-ed. Interior nodes must have
+ * {@link DmtData#FORMAT_NODE} format, and this code must not be returned
+ * for leaf nodes. If no meta-data is provided for a node, all applicable
+ * formats are considered valid (with the above constraints regarding
+ * interior and leaf nodes).
+ * <p>
+ * Note that the 'format' term is a legacy from OMA DM, it is more customary
+ * to think of this as 'type'.
+ * <p>
+ * The formats returned by this method are not checked by DmtAdmin, they
+ * are only for external use, for example in user interfaces. DmtAdmin only
+ * calls {@link #isValidValue} for checking the value, its behaviour should
+ * be consistent with this method.
+ *
+ * @return the allowed format(s) of the node
+ */
+ int getFormat();
+
+ /**
+ * Get the format names for any raw formats supported by the node. This
+ * method is only meaningful if the list of supported formats returned by
+ * {@link #getFormat()} contains {@link DmtData#FORMAT_RAW_STRING} or
+ * {@link DmtData#FORMAT_RAW_BINARY}: it specifies precisely which raw
+ * format(s) are actually supported. If the node cannot contain data in one
+ * of the raw types, this method must return <code>null</code>.
+ * <p>
+ * The format names returned by this method are not checked by DmtAdmin,
+ * they are only for external use, for example in user interfaces. DmtAdmin
+ * only calls {@link #isValidValue} for checking the value, its behaviour
+ * should be consistent with this method.
+ *
+ * @return the allowed format name(s) of raw data stored by the node, or
+ * <code>null</code> if raw formats are not supported
+ */
+ String[] getRawFormatNames();
+
+ /**
+ * Checks whether the given value is valid for this node. This method can be
+ * used to ensure that the value has the correct format and range, that it
+ * is well formed, etc. This method should be consistent with the
+ * constraints defined by the {@link #getFormat}, {@link #getValidValues},
+ * {@link #getMin} and {@link #getMax} methods (if applicable), as the Dmt
+ * Admin only calls this method for value validation.
+ * <p>
+ * This method may return <code>true</code> even if not all aspects of the
+ * value have been checked, expensive operations (for example those that
+ * require external resources) need not be performed here. The actual value
+ * setting method may still indicate that the value is invalid.
+ *
+ * @param value the value to check for validity
+ * @return <code>false</code> if the specified value is found to be
+ * invalid for the node described by this meta-node,
+ * <code>true</code> otherwise
+ */
+ boolean isValidValue(DmtData value);
+
+ /**
+ * Return an array of Strings if valid names are defined for the node, or
+ * <code>null</code> if no valid name list is defined or if this piece of
+ * meta info is not supported. If no meta-data is provided for a node, all
+ * names are considered valid.
+ * <p>
+ * The information returned by this method is not checked by DmtAdmin, it
+ * is only for external use, for example in user interfaces. DmtAdmin only
+ * calls {@link #isValidName} for checking the name, its behaviour should be
+ * consistent with this method.
+ *
+ * @return the valid values for this node name, or <code>null</code> if
+ * not defined
+ */
+ String[] getValidNames();
+
+ /**
+ * Checks whether the given name is a valid name for this node. This method
+ * can be used for example to ensure that the node name is always one of a
+ * predefined set of valid names, or that it matches a specific pattern.
+ * This method should be consistent with the values returned by
+ * {@link #getValidNames} (if any), the DmtAdmin only calls this method for
+ * name validation.
+ * <p>
+ * This method may return <code>true</code> even if not all aspects of the
+ * name have been checked, expensive operations (for example those that
+ * require external resources) need not be performed here. The actual node
+ * creation may still indicate that the node name is invalid.
+ *
+ * @param name the node name to check for validity
+ * @return <code>false</code> if the specified name is found to be invalid
+ * for the node described by this meta-node, <code>true</code>
+ * otherwise
+ */
+ boolean isValidName(String name);
+
+ /**
+ * Returns the list of extension property keys, if the provider of this
+ * <code>MetaNode</code> provides proprietary extensions to node meta
+ * data. The method returns <code>null</code> if the node doesn't provide
+ * such extensions.
+ *
+ * @return the array of supported extension property keys
+ */
+ String[] getExtensionPropertyKeys();
+
+ /**
+ * Returns the value for the specified extension property key. This method
+ * only works if the provider of this <code>MetaNode</code> provides
+ * proprietary extensions to node meta data.
+ *
+ * @param key the key for the extension property
+ * @return the value of the requested property, cannot be <code>null</code>
+ * @throws IllegalArgumentException if the specified key is not supported by
+ * this <code>MetaNode</code>
+ */
+ Object getExtensionProperty(String key);
+}
diff --git a/org.osgi.compendium/src/main/java/info/dmtree/Uri.java b/org.osgi.compendium/src/main/java/info/dmtree/Uri.java
new file mode 100644
index 0000000..c9ad62c
--- /dev/null
+++ b/org.osgi.compendium/src/main/java/info/dmtree/Uri.java
@@ -0,0 +1,563 @@
+/*
+ * $Header: /cvshome/build/info.dmtree/src/info/dmtree/Uri.java,v 1.12 2006/10/24 17:54:28 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package info.dmtree;
+
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.*;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class contains static utility methods to manipulate DMT URIs.
+ * <p>
+ * Syntax of valid DMT URIs:
+ * <ul>
+ * <li>A slash (<code>'/'</code> \u002F) is the separator of the node names.
+ * Slashes used in node name must therefore be escaped using a backslash slash
+ * (<code>"\/"</code>). The backslash must be escaped with a double backslash
+ * sequence. A backslash found must be ignored when it is not followed by a
+ * slash or backslash.
+ * <li>The node name can be constructed using full Unicode character set
+ * (except the Supplementary code, not being supported by CLDC/CDC). However,
+ * using the full Unicode character set for node names is discouraged because
+ * the encoding in the underlying storage as well as the encoding needed in
+ * communications can create significant performance and memory usage overhead.
+ * Names that are restricted to the URI set <code>[-a-zA-Z0-9_.!~*'()]</code>
+ * are most efficient.
+ * <li>URIs used in the DMT must be treated and interpreted as case sensitive.
+ * <li>No End Slash: URI must not end with the delimiter slash (<code>'/'</code>
+ * \u002F). This implies that the root node must be denoted as
+ * <code>"."</code> and not <code>"./"</code>.
+ * <li>No parent denotation: URI must not be constructed using the character
+ * sequence <code>"../"</code> to traverse the tree upwards.
+ * <li>Single Root: The character sequence <code>"./"</code> must not be used
+ * anywhere else but in the beginning of a URI.
+ * </ul>
+ */
+public final class Uri {
+ /*
+ * NOTE: An implementor may also choose to replace this class in
+ * their distribution with a class that directly interfaces with the
+ * info.dmtree implementation. This replacement class MUST NOT alter the
+ * public/protected signature of this class.
+ */
+
+ /*
+ * This class will load the class named
+ * by the org.osgi.vendor.dmtree.DigestDelegate property. This class will call
+ * the public static byte[] digest(byte[]) method on that class.
+ */
+
+ private static class ImplHolder implements PrivilegedAction {
+ // the name of the system property containing the digest delegate class name
+ private static final String DIGEST_DELEGATE_PROPERTY =
+ "org.osgi.vendor.dmtree.DigestDelegate";
+
+ // the Method where message digest requests can be delegated
+ static final Method digestMethod;
+
+ static {
+ digestMethod = (Method) AccessController.doPrivileged(new ImplHolder());
+ }
+
+ private ImplHolder() {
+ }
+
+ public Object run() {
+ String className = System
+ .getProperty(DIGEST_DELEGATE_PROPERTY);
+ if (className == null) {
+ throw new NoClassDefFoundError("Digest " +
+ "delegate class property '" +
+ DIGEST_DELEGATE_PROPERTY +
+ "' must be set to a " +
+ "class which implements a " +
+ "public static byte[] digest(byte[]) method.");
+ }
+
+ Class delegateClass;
+ try {
+ delegateClass = Class.forName(className);
+ }
+ catch (ClassNotFoundException e) {
+ throw new NoClassDefFoundError(e.toString());
+ }
+
+ Method result;
+ try {
+ result = delegateClass.getMethod("digest",
+ new Class[] {byte[].class});
+ }
+ catch (NoSuchMethodException e) {
+ throw new NoSuchMethodError(e.toString());
+ }
+
+ if (!Modifier.isStatic(result.getModifiers())) {
+ throw new NoSuchMethodError(
+ "digest method must be static");
+ }
+
+ return result;
+ }
+ }
+
+
+ // the name of the system property containing the URI segment length limit
+ private static final String SEGMENT_LENGTH_LIMIT_PROPERTY =
+ "org.osgi.impl.service.dmt.uri.limits.segmentlength";
+
+ // the smallest valid value for the URI segment length limit
+ private static final int MINIMAL_SEGMENT_LENGTH_LIMIT = 32;
+
+ // contains the maximum length of node names
+ private static final int segmentLengthLimit;
+
+ static {
+ segmentLengthLimit = ((Integer) AccessController
+ .doPrivileged(new PrivilegedAction() {
+ public Object run() {
+ String limitString = System.getProperty(SEGMENT_LENGTH_LIMIT_PROPERTY);
+ int limit = MINIMAL_SEGMENT_LENGTH_LIMIT; // min. used as default
+
+ try {
+ int limitInt = Integer.parseInt(limitString);
+ if(limitInt >= MINIMAL_SEGMENT_LENGTH_LIMIT)
+ limit = limitInt;
+ } catch(NumberFormatException e) {}
+
+ return new Integer(limit);
+ }
+ })).intValue();
+ }
+
+ // base64 encoding table, modified for use in node name mangling
+ private static final char BASE_64_TABLE[] = {
+ 'A','B','C','D','E','F','G','H',
+ 'I','J','K','L','M','N','O','P',
+ 'Q','R','S','T','U','V','W','X',
+ 'Y','Z','a','b','c','d','e','f',
+ 'g','h','i','j','k','l','m','n',
+ 'o','p','q','r','s','t','u','v',
+ 'w','x','y','z','0','1','2','3',
+ '4','5','6','7','8','9','+','_', // !!! this differs from base64
+ };
+
+
+ /**
+ * A private constructor to suppress the default public constructor.
+ */
+ private Uri() {}
+
+ /**
+ * Returns a node name that is valid for the tree operation methods, based
+ * on the given node name. This transformation is not idempotent, so it must
+ * not be called with a parameter that is the result of a previous
+ * <code>mangle</code> method call.
+ * <p>
+ * Node name mangling is needed in the following cases:
+ * <ul>
+ * <li>if the name contains '/' or '\' characters
+ * <li>if the length of the name exceeds the limit defined by the
+ * implementation
+ * </ul>
+ * <p>
+ * A node name that does not suffer from either of these problems is
+ * guaranteed to remain unchanged by this method. Therefore the client may
+ * skip the mangling if the node name is known to be valid (though it is
+ * always safe to call this method).
+ * <p>
+ * The method returns the normalized <code>nodeName</code> as described
+ * below. Invalid node names are normalized in different ways, depending on
+ * the cause. If the length of the name does not exceed the limit, but the
+ * name contains '/' or '\' characters, then these are simply escaped by
+ * inserting an additional '\' before each occurrence. If the length of the
+ * name does exceed the limit, the following mechanism is used to normalize
+ * it:
+ * <ul>
+ * <li>the SHA 1 digest of the name is calculated
+ * <li>the digest is encoded with the base 64 algorithm
+ * <li>all '/' characters in the encoded digest are replaced with '_'
+ * <li>trailing '=' signs are removed
+ * </ul>
+ *
+ * @param nodeName the node name to be mangled (if necessary), must not be
+ * <code>null</code> or empty
+ * @return the normalized node name that is valid for tree operations
+ * @throws NullPointerException if <code>nodeName</code> is
+ * <code>null</code>
+ * @throws IllegalArgumentException if <code>nodeName</code> is empty
+ */
+ public static String mangle(String nodeName) {
+ return mangle(nodeName, getMaxSegmentNameLength());
+ }
+
+ /**
+ * Construct a URI from the specified URI segments. The segments must
+ * already be mangled.
+ * <p>
+ * If the specified path is an empty array then an empty URI
+ * (<code>""</code>) is returned.
+ *
+ * @param path a possibly empty array of URI segments, must not be
+ * <code>null</code>
+ * @return the URI created from the specified segments
+ * @throws NullPointerException if the specified path or any of its
+ * segments are <code>null</code>
+ * @throws IllegalArgumentException if the specified path contains too many
+ * or malformed segments or the resulting URI is too long
+ */
+ public static String toUri(String[] path) {
+ if (0 == path.length) {
+ return "";
+ }
+
+ if (path.length > getMaxUriSegments()) {
+ throw new IllegalArgumentException(
+ "Path contains too many segments.");
+ }
+
+ StringBuffer uri = new StringBuffer();
+ int uriLength = 0;
+ for (int i = 0; i < path.length; ++i) {
+ // getSegmentLength throws exceptions on malformed segments.
+ int segmentLength = getSegmentLength(path[i]);
+ if (segmentLength > getMaxSegmentNameLength()) {
+ throw new IllegalArgumentException("URI segment too long.");
+ }
+ if (i > 0) {
+ uri.append('/');
+ uriLength++;
+ }
+ uriLength += segmentLength;
+ uri.append(path[i]);
+ }
+ if (uriLength > getMaxUriLength()) {
+ throw new IllegalArgumentException("URI too long.");
+ }
+ return uri.toString();
+ }
+
+ /**
+ * This method returns the length of a URI segment. The length of the URI
+ * segment is defined as the number of bytes in the unescaped, UTF-8 encoded
+ * represenation of the segment.
+ * <p>
+ * The method verifies that the URI segment is well-formed.
+ *
+ * @param segment the URI segment
+ * @return URI segment length
+ * @throws NullPointerException if the specified segment is
+ * <code>null</code>
+ * @throws IllegalArgumentException if the specified URI segment is
+ * malformed
+ */
+ private static int getSegmentLength(String segment) {
+ if (segment.length() == 0)
+ throw new IllegalArgumentException("URI segment is empty.");
+
+ StringBuffer newsegment = new StringBuffer(segment);
+ int i = 0;
+ while (i < newsegment.length()) { // length can decrease during the loop!
+ if (newsegment.charAt(i) == '\\') {
+ if (i == newsegment.length() - 1) // last character cannot be a '\'
+ throw new IllegalArgumentException(
+ "URI segment ends with the escape character.");
+
+ newsegment.deleteCharAt(i); // remove the extra '\'
+ } else if (newsegment.charAt(i) == '/')
+ throw new IllegalArgumentException(
+ "URI segment contains an unescaped '/' character.");
+
+ i++;
+ }
+
+ if (newsegment.toString().equals(".."))
+ throw new IllegalArgumentException(
+ "URI segment must not be \"..\".");
+
+ try {
+ return newsegment.toString().getBytes("UTF-8").length;
+ } catch (UnsupportedEncodingException e) {
+ // This should never happen. All implementations must support
+ // UTF-8 encoding;
+ throw new RuntimeException(e.toString());
+ }
+ }
+
+ /**
+ * Split the specified URI along the path separator '/' charaters and return
+ * an array of URI segments. Special characters in the returned segments are
+ * escaped. The returned array may be empty if the specifed URI was empty.
+ *
+ * @param uri the URI to be split, must not be <code>null</code>
+ * @return an array of URI segments created by splitting the specified URI
+ * @throws NullPointerException if the specified URI is <code>null</code>
+ * @throws IllegalArgumentException if the specified URI is malformed
+ */
+ public static String[] toPath(String uri) {
+ if (uri == null)
+ throw new NullPointerException("'uri' parameter is null.");
+
+ if (!isValidUri(uri))
+ throw new IllegalArgumentException("Malformed URI: " + uri);
+
+ if (uri.length() == 0)
+ return new String[] {};
+
+ List segments = new ArrayList();
+ StringBuffer segment = new StringBuffer();
+
+ boolean escape = false;
+ for (int i = 0; i < uri.length(); i++) {
+ char ch = uri.charAt(i);
+
+ if (escape) {
+ if(ch == '/' || ch == '\\')
+ segment.append('\\');
+ segment.append(ch);
+ escape = false;
+ } else if (ch == '/') {
+ segments.add(segment.toString());
+ segment = new StringBuffer();
+ } else if (ch == '\\') {
+ escape = true;
+ } else
+ segment.append(ch);
+ }
+ if (segment.length() > 0) {
+ segments.add(segment.toString());
+ }
+
+ return (String[]) segments.toArray(new String[segments.size()]);
+ }
+
+ /**
+ * Returns the maximum allowed number of URI segments. The returned value is
+ * implementation specific.
+ * <p>
+ * The return value of <code>Integer.MAX_VALUE</code> indicates that there
+ * is no upper limit on the number of URI segments.
+ *
+ * @return maximum number of URI segments supported by the implementation
+ */
+ public static int getMaxUriSegments() {
+ return Integer.MAX_VALUE;
+ }
+
+ /**
+ * Returns the maximum allowed length of a URI. The value is implementation
+ * specific. The length of the URI is defined as the number of bytes in the
+ * unescaped, UTF-8 encoded represenation of the URI.
+ * <p>
+ * The return value of <code>Integer.MAX_VALUE</code> indicates that there
+ * is no upper limit on the length of URIs.
+ *
+ * @return maximum URI length supported by the implementation
+ */
+ public static int getMaxUriLength() {
+ return Integer.MAX_VALUE;
+ }
+
+ /**
+ * Returns the maximum allowed length of a URI segment. The value is
+ * implementation specific. The length of the URI segment is defined as the
+ * number of bytes in the unescaped, UTF-8 encoded represenation of the
+ * segment.
+ * <p>
+ * The return value of <code>Integer.MAX_VALUE</code> indicates that there
+ * is no upper limit on the length of segment names.
+ *
+ * @return maximum URI segment length supported by the implementation
+ */
+ public static int getMaxSegmentNameLength() {
+ return segmentLengthLimit;
+ }
+
+ /**
+ * Checks whether the specified URI is an absolute URI. An absolute URI
+ * contains the complete path to a node in the DMT starting from the DMT
+ * root (".").
+ *
+ * @param uri the URI to be checked, must not be <code>null</code> and must
+ * contain a valid URI
+ * @return whether the specified URI is absolute
+ * @throws NullPointerException if the specified URI is <code>null</code>
+ * @throws IllegalArgumentException if the specified URI is malformed
+ */
+ public static boolean isAbsoluteUri(String uri) {
+ if( null == uri ) {
+ throw new NullPointerException("'uri' parameter is null.");
+ }
+ if( !isValidUri(uri) )
+ throw new IllegalArgumentException("Malformed URI: " + uri);
+ return uri.equals(".") || uri.equals("\\.") || uri.startsWith("./")
+ || uri.startsWith("\\./");
+ }
+
+ /**
+ * Checks whether the specified URI is valid. A URI is considered valid if
+ * it meets the following constraints:
+ * <ul>
+ * <li>the URI is not <code>null</code>;
+ * <li>the URI follows the syntax defined for valid DMT URIs;
+ * <li>the length of the URI is not more than {@link #getMaxUriLength()};
+ * <li>the URI doesn't contain more than {@link #getMaxUriSegments()}
+ * segments;
+ * <li>the length of each segment of the URI is less than or equal to
+ * {@link #getMaxSegmentNameLength()}.
+ * </ul>
+ * The exact definition of the length of a URI and its segments is
+ * given in the descriptions of the <code>getMaxUriLength()</code> and
+ * <code>getMaxSegmentNameLength()</code> methods.
+ *
+ * @param uri the URI to be validated
+ * @return whether the specified URI is valid
+ */
+ public static boolean isValidUri(String uri) {
+ if (null == uri)
+ return false;
+
+ int paramLen = uri.length();
+ if( paramLen == 0 )
+ return true;
+ if( uri.charAt(0) == '/' || uri.charAt(paramLen-1) == '\\' )
+ return false;
+
+ int processedUriLength = 0;
+ int segmentNumber = 0;
+
+ // append a '/' to indicate the end of the last segment (the URI in the
+ // parameter must not end with a '/')
+ uri += '/';
+ paramLen++;
+
+ int start = 0;
+ for(int i = 1; i < paramLen; i++) { // first character is not a '/'
+ if(uri.charAt(i) == '/' && uri.charAt(i-1) != '\\') {
+ segmentNumber++;
+
+ String segment = uri.substring(start, i);
+ if(segmentNumber > 1 && segment.equals("."))
+ return false; // the URI contains the "." node name at a
+ // position other than the beginning of the URI
+
+ int segmentLength;
+ try {
+ // also checks that the segment is valid
+ segmentLength = getSegmentLength(segment);
+ } catch(IllegalArgumentException e) {
+ return false;
+ }
+
+ if(segmentLength > getMaxSegmentNameLength())
+ return false;
+
+ // the extra byte is for the separator '/' (will be deducted
+ // again for the last segment of the URI)
+ processedUriLength += segmentLength + 1;
+ start = i+1;
+ }
+ }
+
+ processedUriLength--; // remove the '/' added to the end of the URI
+
+ return segmentNumber <= getMaxUriSegments() &&
+ processedUriLength <= getMaxUriLength();
+ }
+
+ // Non-public fields and methods
+
+ // package private method for testing purposes
+ static String mangle(String nodeName, int limit) {
+ if(nodeName == null)
+ throw new NullPointerException(
+ "The 'nodeName' parameter must not be null.");
+
+ if(nodeName.equals(""))
+ throw new IllegalArgumentException(
+ "The 'nodeName' parameter must not be empty.");
+
+ if(nodeName.length() > limit)
+ // create node name hash
+ return getHash(nodeName);
+
+ // escape any '/' and '\' characters in the node name
+ StringBuffer nameBuffer = new StringBuffer(nodeName);
+ for(int i = 0; i < nameBuffer.length(); i++) // 'i' can increase in loop
+ if(nameBuffer.charAt(i) == '\\' || nameBuffer.charAt(i) == '/')
+ nameBuffer.insert(i++, '\\');
+
+ return nameBuffer.toString();
+ }
+
+ private static String getHash(String from) {
+ byte[] byteStream;
+ try {
+ byteStream = from.getBytes("UTF-8");
+ }
+ catch (UnsupportedEncodingException e) {
+ // There's no way UTF-8 encoding is not implemented...
+ throw new IllegalStateException("there's no UTF-8 encoder here!");
+ }
+ byte[] digest = digestMessage(byteStream);
+
+ // very dumb base64 encoder code. There is no need for multiple lines
+ // or trailing '='-s....
+ // also, we hardcoded the fact that sha-1 digests are 20 bytes long
+ StringBuffer sb = new StringBuffer(digest.length*2);
+ for(int i=0;i<6;i++) {
+ int d0 = digest[i*3]&0xff;
+ int d1 = digest[i*3+1]&0xff;
+ int d2 = digest[i*3+2]&0xff;
+ sb.append(BASE_64_TABLE[d0>>2]);
+ sb.append(BASE_64_TABLE[(d0<<4|d1>>4)&63]);
+ sb.append(BASE_64_TABLE[(d1<<2|d2>>6)&63]);
+ sb.append(BASE_64_TABLE[d2&63]);
+ }
+ int d0 = digest[18]&0xff;
+ int d1 = digest[19]&0xff;
+ sb.append(BASE_64_TABLE[d0>>2]);
+ sb.append(BASE_64_TABLE[(d0<<4|d1>>4)&63]);
+ sb.append(BASE_64_TABLE[(d1<<2)&63]);
+
+ return sb.toString();
+ }
+
+ private static byte[] digestMessage(byte[] byteStream) {
+ try {
+ try {
+ return (byte[]) ImplHolder.digestMethod.invoke(null, new Object[] {
+ byteStream});
+ }
+ catch (InvocationTargetException e) {
+ throw e.getTargetException();
+ }
+ }
+ catch (Error e) {
+ throw e;
+ }
+ catch (RuntimeException e) {
+ throw e;
+ }
+ catch (Throwable e) {
+ throw new RuntimeException(e.toString());
+ }
+ }
+}
diff --git a/org.osgi.compendium/src/main/java/info/dmtree/notification/AlertItem.java b/org.osgi.compendium/src/main/java/info/dmtree/notification/AlertItem.java
new file mode 100644
index 0000000..2088743
--- /dev/null
+++ b/org.osgi.compendium/src/main/java/info/dmtree/notification/AlertItem.java
@@ -0,0 +1,170 @@
+/*
+ * $Header: /cvshome/build/info.dmtree/src/info/dmtree/notification/AlertItem.java,v 1.3 2006/07/04 12:26:50 tszeredi Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package info.dmtree.notification;
+
+import info.dmtree.DmtData;
+import info.dmtree.Uri;
+
+/**
+ * Immutable data structure carried in an alert (client initiated notification).
+ * The <code>AlertItem</code> describes details of various notifications that
+ * can be sent by the client, for example as alerts in the OMA DM protocol. The
+ * use cases include the client sending a session request to the server (alert
+ * 1201), the client notifying the server of completion of a software update
+ * operation (alert 1226) or sending back results in response to an asynchronous
+ * EXEC command.
+ * <p>
+ * The data syntax and semantics varies widely between various alerts, so does
+ * the optionality of particular parameters of an alert item. If an item, such
+ * as source or type, is not defined, the corresponding getter method returns
+ * <code>null</code>. For example, for alert 1201 (client-initiated session)
+ * all elements will be <code>null</code>.
+ * <P>
+ * The syntax used in <code>AlertItem</code> class corresponds to the OMA DM
+ * alert format. {@link NotificationService} implementations on other management
+ * protocols should map these constructs to the underlying protocol.
+ */
+public class AlertItem {
+
+ private final String source;
+
+ private final String type;
+
+ private final String mark;
+
+ private final DmtData data;
+
+ /**
+ * Create an instance of the alert item. The constructor takes all possible
+ * data entries as parameters. Any of these parameters can be
+ * <code>null</code>. The semantics of the parameters may be refined by
+ * the definition of a specific alert, identified by its alert code (see
+ * {@link NotificationService#sendNotification}). In case of Generic Alerts
+ * for example (code 1226), the <code>mark</code> parameter contains a
+ * severity string.
+ *
+ * @param source the URI of the node which is the source of the alert item
+ * @param type a MIME type or a URN that identifies the type of the data in
+ * the alert item
+ * @param data a <code>DmtData</code> object that contains the format and
+ * value of the data in the alert item
+ * @param mark the mark parameter of the alert item
+ */
+ public AlertItem(String source, String type, String mark, DmtData data) {
+ this.source = source;
+ this.type = type;
+ this.mark = mark;
+ this.data = data;
+ }
+
+ /**
+ * Create an instance of the alert item, specifying the source node URI as
+ * an array of path segments. The constructor takes all possible data
+ * entries as parameters. Any of these parameters can be <code>null</code>.
+ * The semantics of the parameters may be refined by the definition of a
+ * specific alert, identified by its alert code (see
+ * {@link NotificationService#sendNotification}). In case of Generic Alerts
+ * for example (code 1226), the <code>mark</code> parameter contains a
+ * severity string.
+ *
+ * @param source the path of the node which is the source of the alert item
+ * @param type a MIME type or a URN that identifies the type of the data in
+ * the alert item
+ * @param data a <code>DmtData</code> object that contains the format and
+ * value of the data in the alert item
+ * @param mark the mark parameter of the alert item
+ */
+ public AlertItem(String[] source, String type, String mark, DmtData data) {
+ if ((null == source)) {
+ this.source = null;
+ } else {
+ this.source = Uri.toUri(source);
+ }
+ this.type = type;
+ this.mark = mark;
+ this.data = data;
+ }
+
+ /**
+ * Get the node which is the source of the alert. There might be no source
+ * associated with the alert item.
+ *
+ * @return the URI of the node which is the source of this alert, or
+ * <code>null</code> if there is no source
+ */
+ public String getSource() {
+ return source;
+ }
+
+ /**
+ * Get the type associated with the alert item. The type string is a MIME
+ * type or a URN that identifies the type of the data in the alert item
+ * (returned by {@link #getData}). There might be no type associated with
+ * the alert item.
+ *
+ * @return the type type associated with the alert item, or
+ * <code>null</code> if there is no type
+ */
+ public String getType() {
+ return type;
+ }
+
+ /**
+ * Get the mark parameter associated with the alert item. The interpretation
+ * of the mark parameter depends on the alert being sent, as identified by
+ * the alert code in {@link NotificationService#sendNotification}. There
+ * might be no mark associated with the alert item.
+ *
+ * @return the mark associated with the alert item, or <code>null</code>
+ * if there is no mark
+ */
+ public String getMark() {
+ return mark;
+ }
+
+ /**
+ * Get the data associated with the alert item. The returned
+ * <code>DmtData</code> object contains the format and the value of the
+ * data in the alert item. There might be no data associated with the alert
+ * item.
+ *
+ * @return the data associated with the alert item, or <code>null</code>
+ * if there is no data
+ */
+ public DmtData getData() {
+ return data;
+ }
+
+ /**
+ * Returns the string representation of this alert item. The returned string
+ * includes all parameters of the alert item, and has the following format:
+ *
+ * <pre>
+ * AlertItem(<source>, <type>, <mark>, <data>)
+ * </pre>
+ *
+ * The last parameter is the string representation of the data value. The
+ * format of the data is not explicitly included.
+ *
+ * @return the string representation of this alert item
+ */
+ public String toString() {
+ return "AlertItem(" + source + ", " + type + ", " + mark + ", " + data
+ + ")";
+ }
+}
diff --git a/org.osgi.compendium/src/main/java/info/dmtree/notification/NotificationService.java b/org.osgi.compendium/src/main/java/info/dmtree/notification/NotificationService.java
new file mode 100644
index 0000000..6ee9cd3
--- /dev/null
+++ b/org.osgi.compendium/src/main/java/info/dmtree/notification/NotificationService.java
@@ -0,0 +1,99 @@
+/*
+ * $Header: /cvshome/build/info.dmtree/src/info/dmtree/notification/NotificationService.java,v 1.5 2006/07/04 12:26:50 tszeredi Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package info.dmtree.notification;
+
+import info.dmtree.DmtException;
+import info.dmtree.DmtSession;
+
+/**
+ * NotificationService enables sending aynchronous notifications to a management
+ * server. The implementation of <code>NotificationService</code> should
+ * register itself in the OSGi service registry as a service.
+ */
+public interface NotificationService {
+
+ /**
+ * Sends a notification to a named principal. It is the responsibility of
+ * the <code>NotificationService</code> to route the notification to the
+ * given principal using the registered
+ * {@link info.dmtree.notification.spi.RemoteAlertSender} services.
+ * <p>
+ * In remotely initiated sessions the principal name identifies the remote
+ * server that created the session, this can be obtained using the session's
+ * {@link DmtSession#getPrincipal getPrincipal} call.
+ * <p>
+ * The principal name may be omitted if the client does not know the
+ * principal name. Even in this case the routing might be possible if the
+ * Notification Service finds an appropriate default destination (for
+ * example if it is only connected to one protocol adapter, which is only
+ * connected to one management server).
+ * <p>
+ * Since sending the notification and receiving acknowledgment for it is
+ * potentially a very time-consuming operation, notifications are sent
+ * asynchronously. This method should attempt to ensure that the
+ * notification can be sent successfully, and should throw an exception if
+ * it detects any problems. If the method returns without error, the
+ * notification is accepted for sending and the implementation must make a
+ * best-effort attempt to deliver it.
+ * <p>
+ * In case the notification is an asynchronous response to a previous
+ * {@link DmtSession#execute(String, String, String) execute} command, a
+ * correlation identifier can be specified to provide the association
+ * between the execute and the notification.
+ * <p>
+ * In order to send a notification using this method, the caller must have
+ * an <code>AlertPermission</code> with a target string matching the
+ * specified principal name. If the <code>principal</code> parameter is
+ * <code>null</code> (the principal name is not known), the target of the
+ * <code>AlertPermission</code> must be "*".
+ * <p>
+ * When this method is called with all its parameters <code>null</code> or 0
+ * (except <code>principal</code>), it should send a protocol
+ * specific default notification to initiate a management session. For
+ * example, in case of OMA DM this is alert 1201 "Client Initiated Session".
+ * The <code>principal</code> parameter can be used to determine the
+ * recipient of the session initiation request.
+ *
+ * @param principal the principal name which is the recipient of this
+ * notification, can be <code>null</code>
+ * @param code the alert code, can be 0 if not needed
+ * @param correlator optional field that contains the correlation identifier
+ * of an associated exec command, can be <code>null</code> if not
+ * needed
+ * @param items the data of the alert items carried in this alert, can be
+ * <code>null</code> or empty if not needed
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>UNAUTHORIZED</code> when the remote server rejected
+ * the request due to insufficient authorization
+ * <li><code>ALERT_NOT_ROUTED</code> when the alert can not be
+ * routed to the given principal
+ * <li><code>REMOTE_ERROR</code> in case of communication
+ * problems between the device and the destination
+ * <li><code>COMMAND_FAILED</code> for unspecified errors
+ * encountered while attempting to complete the command
+ * <li><code>FEATURE_NOT_SUPPORTED</code> if the underlying
+ * management protocol doesn't support asynchronous notifications
+ * </ul>
+ * @throws SecurityException if the caller does not have the required
+ * <code>AlertPermission</code> with a target matching the
+ * <code>principal</code> parameter, as described above
+ */
+ void sendNotification(String principal, int code, String correlator,
+ AlertItem[] items) throws DmtException;
+}
diff --git a/org.osgi.compendium/src/main/java/info/dmtree/notification/spi/RemoteAlertSender.java b/org.osgi.compendium/src/main/java/info/dmtree/notification/spi/RemoteAlertSender.java
new file mode 100644
index 0000000..3e28892
--- /dev/null
+++ b/org.osgi.compendium/src/main/java/info/dmtree/notification/spi/RemoteAlertSender.java
@@ -0,0 +1,84 @@
+/*
+ * $Header: /cvshome/build/info.dmtree/src/info/dmtree/notification/spi/RemoteAlertSender.java,v 1.2 2006/06/16 16:31:59 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package info.dmtree.notification.spi;
+
+import info.dmtree.notification.AlertItem;
+
+/**
+ * The RemoteAlertSender can be used to send notifications to (remote) entities
+ * identified by principal names. This service is provided by Protocol Adapters,
+ * and is used by the
+ * {@link info.dmtree.notification.NotificationService} when sending
+ * alerts. Implementations of this interface have to be able to connect and send
+ * alerts to one or more management servers in a protocol specific way.
+ * <p>
+ * The properties of the service registration should specify a list of
+ * destinations (principals) where the service is capable of sending alerts.
+ * This can be done by providing a <code>String</code> array of principal
+ * names in the <code>principals</code> registration property. If this property
+ * is not registered, the service will be treated as the default sender. The
+ * default alert sender is only used when a more specific alert sender cannot be
+ * found.
+ * <p>
+ * The <code>principals</code> registration property is used when the
+ * {@link info.dmtree.notification.NotificationService#sendNotification}
+ * method is called, to find the proper <code>RemoteAlertSender</code> for the
+ * given destination. If the caller does not specify a principal, the alert is
+ * only sent if the Notification Sender finds a default alert sender, or if the
+ * choice is unambiguous for some other reason (for example if only one alert
+ * sender is registered).
+ */
+public interface RemoteAlertSender {
+ /**
+ * Sends an alert to a server identified by its principal name. In case the
+ * alert is sent in response to a previous
+ * {@link info.dmtree.DmtSession#execute(String, String, String) execute}
+ * command, a correlation identifier can be specified to provide the
+ * association between the execute and the alert.
+ * <p>
+ * The <code>principal</code> parameter specifies which server the alert
+ * should be sent to. This parameter can be <code>null</code> if the
+ * client does not know the name of the destination. The alert should still
+ * be delivered if possible; for example if the alert sender is only
+ * connected to one destination.
+ * <p>
+ * Any exception thrown on this method will be propagated to the original
+ * sender of the event, wrapped in a <code>DmtException</code> with the
+ * code <code>REMOTE_ERROR</code>.
+ * <p>
+ * Since sending the alert and receiving acknowledgment for it is
+ * potentially a very time-consuming operation, alerts are sent
+ * asynchronously. This method should attempt to ensure that the alert can
+ * be sent successfully, and should throw an exception if it detects any
+ * problems. If the method returns without error, the alert is accepted for
+ * sending and the implementation must make a best-effort attempt to deliver
+ * it.
+ *
+ * @param principal the name identifying the server where the alert should
+ * be sent, can be <code>null</code>
+ * @param code the alert code, can be 0 if not needed
+ * @param correlator the correlation identifier of an associated EXEC
+ * command, or <code>null</code> if there is no associated EXEC
+ * @param items the data of the alert items carried in this alert, can be
+ * empty or <code>null</code> if no alert items are needed
+ * @throws Exception if the alert can not be sent to the server
+ */
+ void sendAlert(String principal, int code, String correlator,
+ AlertItem[] items) throws Exception;
+}
diff --git a/org.osgi.compendium/src/main/java/info/dmtree/registry/DmtServiceFactory.java b/org.osgi.compendium/src/main/java/info/dmtree/registry/DmtServiceFactory.java
new file mode 100644
index 0000000..9820277
--- /dev/null
+++ b/org.osgi.compendium/src/main/java/info/dmtree/registry/DmtServiceFactory.java
@@ -0,0 +1,96 @@
+/*
+ * $Header: /cvshome/build/info.dmtree/src/info/dmtree/registry/DmtServiceFactory.java,v 1.5 2006/07/11 09:38:25 tszeredi Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package info.dmtree.registry;
+
+import info.dmtree.DmtAdmin;
+import info.dmtree.notification.NotificationService;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+
+
+/**
+ * This class is the central access point for Device Management services.
+ * Applications can use the static factory methods provided in this class to
+ * obtain access to the different Device Management related services, such as
+ * the DmtAdmin for manipulating the tree, or the Notification Service for
+ * sending notifications to management servers.
+ * <p>
+ * These methods are not needed in an OSGi environment, clients should retrieve
+ * the required service objects from the OSGi Service Registry.
+ */
+public final class DmtServiceFactory {
+ private static BundleContext context = null;
+
+ /**
+ * A private constructor to suppress the default public constructor.
+ */
+ private DmtServiceFactory() {}
+
+ /**
+ * This method is used to obtain access to <code>DmtAdmin</code>, which
+ * enables applications to manipulate the Device Management Tree.
+ *
+ * @return a DmtAdmin service object
+ */
+ public static DmtAdmin getDmtAdmin() {
+ if(context == null)
+ throw new IllegalStateException("Cannot retrieve Dmt Admin " +
+ "service, implementation bundle not started yet.");
+
+ ServiceReference dmtAdminRef =
+ context.getServiceReference(DmtAdmin.class.getName());
+ if(dmtAdminRef == null)
+ throw new IllegalStateException("Dmt Admin service not found in " +
+ "service registry.");
+
+ DmtAdmin dmtAdmin = (DmtAdmin) context.getService(dmtAdminRef);
+ if(dmtAdmin == null)
+ throw new IllegalStateException("Dmt Admin service not found in " +
+ "service registry.");
+
+ return dmtAdmin;
+ }
+
+ /**
+ * This method is used to obtain access to <code>NotificationService</code>,
+ * which enables applications to send asynchronous notifications to
+ * management servers.
+ *
+ * @return a NotificationService service object
+ */
+ public static NotificationService getNotificationService() {
+ if(context == null)
+ throw new IllegalStateException("Cannot retrieve Notification " +
+ "service, implementation bundle not started yet.");
+
+ ServiceReference notificationServiceRef =
+ context.getServiceReference(NotificationService.class.getName());
+ if(notificationServiceRef == null)
+ throw new IllegalStateException("Notification service not found " +
+ "in service registry.");
+
+ NotificationService notificationService =
+ (NotificationService) context.getService(notificationServiceRef);
+ if(notificationService == null)
+ throw new IllegalStateException("Notification service not found " +
+ "in service registry.");
+
+ return notificationService;
+ }
+}
diff --git a/org.osgi.compendium/src/main/java/info/dmtree/security/AlertPermission.java b/org.osgi.compendium/src/main/java/info/dmtree/security/AlertPermission.java
new file mode 100644
index 0000000..6fc0209
--- /dev/null
+++ b/org.osgi.compendium/src/main/java/info/dmtree/security/AlertPermission.java
@@ -0,0 +1,261 @@
+/*
+ * $Header: /cvshome/build/info.dmtree/src/info/dmtree/security/AlertPermission.java,v 1.4 2006/07/12 21:21:52 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package info.dmtree.security;
+
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.util.*;
+
+/**
+ * Indicates the callers authority to send alerts to management servers,
+ * identified by their principal names.
+ * <p>
+ * <code>AlertPermission</code> has a target string which controls the
+ * principal names where alerts can be sent. A wildcard is allowed at the end of
+ * the target string, to allow sending alerts to any principal with a name
+ * matching the given prefix. The "*" target means that alerts can be
+ * sent to any destination.
+ */
+public class AlertPermission extends Permission {
+ private static final long serialVersionUID = -3206463101788245739L;
+
+ // specifies whether the target string had a wildcard at the end
+ private final boolean isPrefix;
+
+ // the target string without the wildcard (if there was one)
+ private final String serverId;
+
+ /**
+ * Creates a new <code>AlertPermission</code> object with its name set to
+ * the target string. Name must be non-null and non-empty.
+ *
+ * @param target the name of a principal, can end with <code>*</code> to
+ * match any principal identifier with the given prefix
+ * @throws NullPointerException if <code>name</code> is <code>null</code>
+ * @throws IllegalArgumentException if <code>name</code> is empty
+ */
+ public AlertPermission(String target) {
+ super(target);
+
+ if (target == null)
+ throw new NullPointerException(
+ "'target' parameter must not be null.");
+
+ if (target.equals(""))
+ throw new IllegalArgumentException(
+ "'target' parameter must not be empty.");
+
+ isPrefix = target.endsWith("*");
+ if (isPrefix)
+ serverId = target.substring(0, target.length() - 1);
+ else
+ serverId = target;
+ }
+
+ /**
+ * Creates a new <code>AlertPermission</code> object using the 'canonical'
+ * two argument constructor. In this version this class does not define any
+ * actions, the second argument of this constructor must be "*" so that this
+ * class can later be extended in a backward compatible way.
+ *
+ * @param target the name of the server, can end with <code>*</code> to
+ * match any server identifier with the given prefix
+ * @param actions no actions defined, must be "*" for forward compatibility
+ * @throws NullPointerException if <code>name</code> or
+ * <code>actions</code> is <code>null</code>
+ * @throws IllegalArgumentException if <code>name</code> is empty or
+ * <code>actions</code> is not "*"
+ */
+ public AlertPermission(String target, String actions) {
+ this(target);
+
+ if (actions == null)
+ throw new NullPointerException(
+ "'actions' parameter must not be null.");
+
+ if (!actions.equals("*"))
+ throw new IllegalArgumentException(
+ "'actions' parameter must be \"*\".");
+ }
+
+ /**
+ * Checks whether the given object is equal to this AlertPermission
+ * instance. Two AlertPermission instances are equal if they have the same
+ * target string.
+ *
+ * @param obj the object to compare to this AlertPermission instance
+ * @return <code>true</code> if the parameter represents the same
+ * permissions as this instance
+ */
+ public boolean equals(Object obj) {
+ if (obj == this)
+ return true;
+
+ if (!(obj instanceof AlertPermission))
+ return false;
+
+ AlertPermission other = (AlertPermission) obj;
+
+ return isPrefix == other.isPrefix && serverId.equals(other.serverId);
+ }
+
+ /**
+ * Returns the action list (always <code>*</code> in the current version).
+ *
+ * @return the action string "*"
+ */
+ public String getActions() {
+ return "*";
+ }
+
+ /**
+ * Returns the hash code for this permission object. If two AlertPermission
+ * objects are equal according to the {@link #equals} method, then calling
+ * this method on each of the two AlertPermission objects must produce the
+ * same integer result.
+ *
+ * @return hash code for this permission object
+ */
+ public int hashCode() {
+ return new Boolean(isPrefix).hashCode() ^ serverId.hashCode();
+ }
+
+ /**
+ * Checks if this AlertPermission object implies the specified permission.
+ * Another AlertPermission instance is implied by this permission either if
+ * the target strings are identical, or if this target can be made identical
+ * to the other target by replacing a trailing "*" with any
+ * string.
+ *
+ * @param p the permission to check for implication
+ * @return true if this AlertPermission instance implies the specified
+ * permission
+ */
+ public boolean implies(Permission p) {
+ if (!(p instanceof AlertPermission))
+ return false;
+
+ AlertPermission other = (AlertPermission) p;
+
+ return impliesServer(other);
+ }
+
+ /**
+ * Returns a new PermissionCollection object for storing AlertPermission
+ * objects.
+ *
+ * @return the new PermissionCollection
+ */
+ public PermissionCollection newPermissionCollection() {
+ return new DmtAlertPermissionCollection();
+ }
+
+ /*
+ * Returns true if the server name parameter of the given AlertPermission is
+ * implied by the server name of this permission, i.e. this server name is a
+ * prefix of the other one but ends with a *, or the two server names are
+ * equal.
+ */
+ boolean impliesServer(AlertPermission p) {
+ return isPrefix ? p.serverId.startsWith(serverId) : !p.isPrefix
+ && p.serverId.equals(serverId);
+ }
+}
+
+/**
+ * Represents a homogeneous collection of AlertPermission objects.
+ */
+final class DmtAlertPermissionCollection extends PermissionCollection {
+ private static final long serialVersionUID = 2288462124510043429L;
+
+ private ArrayList perms;
+
+ /**
+ * Create an empty DmtAlertPermissionCollection object.
+ */
+ public DmtAlertPermissionCollection() {
+ perms = new ArrayList();
+ }
+
+ /**
+ * Adds a permission to the DmtAlertPermissionCollection.
+ *
+ * @param permission the Permission object to add
+ * @exception IllegalArgumentException if the permission is not a
+ * AlertPermission
+ * @exception SecurityException if this DmtAlertPermissionCollection object
+ * has been marked readonly
+ */
+ public void add(Permission permission) {
+ if (!(permission instanceof AlertPermission))
+ throw new IllegalArgumentException(
+ "Cannot add permission, invalid permission type: "
+ + permission);
+ if (isReadOnly())
+ throw new SecurityException(
+ "Cannot add permission, collection is marked read-only.");
+
+ // only add new permission if it is not already implied by the
+ // permissions in the collection
+ if (!implies(permission)) {
+ // remove all permissions that are implied by the new one
+ Iterator i = perms.iterator();
+ while (i.hasNext())
+ if (permission.implies((AlertPermission) i.next()))
+ i.remove();
+
+ // no need to synchronize because all adds are done sequentially
+ // before any implies() calls
+ perms.add(permission);
+
+ }
+ }
+
+ /**
+ * Check whether this set of permissions implies the permission specified in
+ * the parameter.
+ *
+ * @param permission the Permission object to compare
+ * @return true if the parameter permission is a proper subset of the
+ * permissions in the collection, false otherwise
+ */
+ public boolean implies(Permission permission) {
+ if (!(permission instanceof AlertPermission))
+ return false;
+
+ AlertPermission other = (AlertPermission) permission;
+
+ Iterator i = perms.iterator();
+ while (i.hasNext())
+ if (((AlertPermission) i.next()).impliesServer(other))
+ return true;
+
+ return false;
+ }
+
+ /**
+ * Returns an enumeration of all the AlertPermission objects in the
+ * container. The returned value cannot be <code>null</code>.
+ *
+ * @return an enumeration of all the AlertPermission objects
+ */
+ public Enumeration elements() {
+ // Convert Iterator into Enumeration
+ return Collections.enumeration(perms);
+ }
+}
diff --git a/org.osgi.compendium/src/main/java/info/dmtree/security/DmtPermission.java b/org.osgi.compendium/src/main/java/info/dmtree/security/DmtPermission.java
new file mode 100644
index 0000000..aa9702b
--- /dev/null
+++ b/org.osgi.compendium/src/main/java/info/dmtree/security/DmtPermission.java
@@ -0,0 +1,442 @@
+/*
+ * $Header: /cvshome/build/info.dmtree/src/info/dmtree/security/DmtPermission.java,v 1.10 2006/10/19 13:32:53 tszeredi Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package info.dmtree.security;
+
+import info.dmtree.Acl;
+import info.dmtree.Uri;
+
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.util.*;
+
+/**
+ * Controls access to management objects in the Device Management Tree (DMT). It
+ * is intended to control local access to the DMT. DmtPermission target string
+ * identifies the management object URI and the action field lists the OMA DM
+ * commands that are permitted on the management object. Example:
+ *
+ * <pre>
+ * DmtPermission("./OSGi/bundles", "Add,Replace,Get");
+ * </pre>
+ *
+ * This means that owner of this permission can execute Add, Replace and Get
+ * commands on the ./OSGi/bundles management object. It is possible to use
+ * wildcards in both the target and the actions field. Wildcard in the target
+ * field means that the owner of the permission can access children nodes of the
+ * target node. Example:
+ *
+ * <pre>
+ * DmtPermission("./OSGi/bundles/*", "Get");
+ * </pre>
+ *
+ * This means that owner of this permission has Get access on every child node
+ * of ./OSGi/bundles. The asterix does not necessarily have to follow a '/'
+ * character. For example the
+ * <code>"./OSGi/a*"</code> target matches the
+ * <code>./OSGi/applications</code> subtree.
+ * <p>
+ * If wildcard is present in the actions field, all legal OMA DM commands are
+ * allowed on the designated nodes(s) by the owner of the permission. Action
+ * names are interpreted case-insensitively, but the canonical action string
+ * returned by {@link #getActions} uses the forms defined by the action
+ * constants.
+ */
+public class DmtPermission extends Permission {
+ private static final long serialVersionUID = -1910969921419407809L;
+
+ /**
+ * Holders of DmtPermission with the Add action present can create new nodes
+ * in the DMT, that is they are authorized to execute the
+ * createInteriorNode() and createLeafNode() methods of the DmtSession. This
+ * action is also required for the copy() command, which needs to perform
+ * node creation operations (among others).
+ */
+ public static final String ADD = "Add";
+
+ /**
+ * Holders of DmtPermission with the Delete action present can delete nodes
+ * from the DMT, that is they are authorized to execute the deleteNode()
+ * method of the DmtSession.
+ */
+ public static final String DELETE = "Delete";
+
+ /**
+ * Holders of DmtPermission with the Exec action present can execute nodes
+ * in the DMT, that is they are authorized to call the execute() method of
+ * the DmtSession.
+ */
+ public static final String EXEC = "Exec";
+
+ /**
+ * Holders of DmtPermission with the Get action present can query DMT node
+ * value or properties, that is they are authorized to execute the
+ * isLeafNode(), getNodeAcl(), getEffectiveNodeAcl(), getMetaNode(),
+ * getNodeValue(), getChildNodeNames(), getNodeTitle(), getNodeVersion(),
+ * getNodeTimeStamp(), getNodeSize() and getNodeType() methods of the
+ * DmtSession. This action is also required for the copy() command, which
+ * needs to perform node query operations (among others).
+ */
+ public static final String GET = "Get";
+
+ /**
+ * Holders of DmtPermission with the Replace action present can update DMT
+ * node value or properties, that is they are authorized to execute the
+ * setNodeAcl(), setNodeTitle(), setNodeValue(), setNodeType() and
+ * renameNode() methods of the DmtSession. This action is also be required
+ * for the copy() command if the original node had a title property (which
+ * must be set in the new node).
+ */
+ public static final String REPLACE = "Replace";
+
+ // does this permission have a wildcard at the end?
+ private final boolean prefixPath;
+
+ // the name without the wildcard on the end
+ private final String path;
+
+ // the actions mask
+ private final int mask;
+
+ // the canonical action string (redundant)
+ private final String actions;
+
+ /**
+ * Creates a new DmtPermission object for the specified DMT URI with the
+ * specified actions. The given URI can be:
+ * <ul>
+ * <li> <code>"*"</code>, which matches all valid
+ * (see {@link Uri#isValidUri}) absolute URIs;
+ * <li> the prefix of an absolute URI followed by the <code>*</code>
+ * character (for example <code>"./OSGi/L*"</code>), which matches all valid
+ * absolute URIs beginning with the given prefix;
+ * <li> a valid absolute URI, which matches itself.
+ * </ul>
+ * <p>
+ * Since the <code>*</code> character is itself a valid URI character, it
+ * can appear as the last character of a valid absolute URI. To distinguish
+ * this case from using <code>*</code> as a wildcard, the <code>*</code>
+ * character at the end of the URI must be escaped with the <code>\</code>
+ * charater. For example the URI <code>"./a*"</code> matches
+ * <code>"./a"</code>, <code>"./aa"</code>, <code>"./a/b"</code> etc. while
+ * <code>"./a\*"</code> matches <code>"./a*"</code> only.
+ * <p>
+ * The actions string must either be "*" to allow all actions, or it must
+ * contain a non-empty subset of the valid actions, defined as constants in
+ * this class.
+ *
+ * @param dmtUri URI of the management object (or subtree)
+ * @param actions OMA DM actions allowed
+ * @throws NullPointerException if any of the parameters are
+ * <code>null</code>
+ * @throws IllegalArgumentException if any of the parameters are invalid
+ */
+ public DmtPermission(String dmtUri, String actions) {
+ super(dmtUri);
+ mask = getMask(actions);
+ this.actions = canonicalActions(mask);
+
+ if (dmtUri == null)
+ throw new NullPointerException("'dmtUri' parameter must not be " +
+ "null.");
+
+ prefixPath = dmtUri.endsWith("*") && !dmtUri.endsWith("\\*");
+
+ if(prefixPath) {
+ dmtUri = dmtUri.substring(0, dmtUri.length() - 1);
+
+ // the single "*" as dmtUri is the only valid non-absolute URI param
+ if(dmtUri.length() == 0) {
+ path = "";
+ return;
+ }
+ }
+
+ // if URI ends with "/*", remove it before the validity check
+ if(prefixPath && dmtUri.endsWith("/") && !dmtUri.endsWith("\\/"))
+ checkUri(dmtUri.substring(0, dmtUri.length() - 1));
+ else
+ checkUri(dmtUri);
+
+ // canonicalize URI: remove escapes from non-special characters
+ StringBuffer sb = new StringBuffer(dmtUri);
+ int i = 0;
+ while(i < sb.length()) { // length can decrease during the loop!
+ if(sb.charAt(i) == '\\') {
+ // there must be a next character after a '\' in a valid URI
+ char nextCh = sb.charAt(i+1);
+ if(nextCh != '/' && nextCh != '\\')
+ sb.deleteCharAt(i); // remove the extra '\'
+ else
+ i++;
+ }
+ i++;
+ }
+ path = sb.toString();
+ }
+
+ private void checkUri(String dmtUri) throws IllegalArgumentException {
+ if(!Uri.isValidUri(dmtUri))
+ throw new IllegalArgumentException("'dmtUri' parameter does not " +
+ "contain a valid URI.");
+
+ if(!Uri.isAbsoluteUri(dmtUri))
+ throw new IllegalArgumentException("'dmtUri' parameter does not " +
+ "contain an absolute URI.");
+ }
+
+ /**
+ * Checks whether the given object is equal to this DmtPermission instance.
+ * Two DmtPermission instances are equal if they have the same target string
+ * and the same action mask. The "*" action mask is considered equal to a
+ * mask containing all actions.
+ *
+ * @param obj the object to compare to this DmtPermission instance
+ * @return <code>true</code> if the parameter represents the same
+ * permissions as this instance
+ */
+ public boolean equals(Object obj) {
+ if (obj == this)
+ return true;
+
+ if (!(obj instanceof DmtPermission))
+ return false;
+
+ DmtPermission other = (DmtPermission) obj;
+
+ return mask == other.mask && prefixPath == other.prefixPath
+ && path.equals(other.path);
+ }
+
+ /**
+ * Returns the String representation of the action list. The allowed actions
+ * are listed in the following order: Add, Delete, Exec, Get, Replace. The
+ * wildcard character is not used in the returned string, even if the class
+ * was created using the "*" wildcard.
+ *
+ * @return canonical action list for this permission object
+ */
+ public String getActions() {
+ return actions;
+ }
+
+ /**
+ * Returns the hash code for this permission object. If two DmtPermission
+ * objects are equal according to the {@link #equals} method, then calling
+ * this method on each of the two DmtPermission objects must produce the
+ * same integer result.
+ *
+ * @return hash code for this permission object
+ */
+ public int hashCode() {
+ return new Integer(mask).hashCode()
+ ^ new Boolean(prefixPath).hashCode() ^ path.hashCode();
+ }
+
+ /**
+ * Checks if this DmtPermission object "implies" the specified
+ * permission. This method returns <code>false</code> if and only if at
+ * least one of the following conditions are fulfilled for the specified
+ * permission:
+ * <ul>
+ * <li>it is not a DmtPermission
+ * <li>its set of actions contains an action not allowed by this permission
+ * <li>the set of nodes defined by its path contains a node not defined by
+ * the path of this permission
+ * </ul>
+ *
+ * @param p the permission to check for implication
+ * @return true if this DmtPermission instance implies the specified
+ * permission
+ */
+ public boolean implies(Permission p) {
+ if (!(p instanceof DmtPermission))
+ return false;
+
+ DmtPermission other = (DmtPermission) p;
+
+ if ((mask & other.mask) != other.mask)
+ return false;
+
+ return impliesPath(other);
+ }
+
+ /**
+ * Returns a new PermissionCollection object for storing DmtPermission
+ * objects.
+ *
+ * @return the new PermissionCollection
+ */
+ public PermissionCollection newPermissionCollection() {
+ return new DmtPermissionCollection();
+ }
+
+ // parses the given action string, and returns the corresponding action mask
+ private static int getMask(String actions) {
+ int mask = 0;
+
+ if (actions == null)
+ throw new NullPointerException(
+ "'actions' parameter cannot be null.");
+
+ if (actions.equals("*"))
+ return Acl.ALL_PERMISSION;
+
+ // empty tokens (swallowed by StringTokenizer) are not considered errors
+ StringTokenizer st = new StringTokenizer(actions, ",");
+ while (st.hasMoreTokens()) {
+ String action = st.nextToken();
+ if (action.equalsIgnoreCase(GET)) {
+ mask |= Acl.GET;
+ } else if (action.equalsIgnoreCase(ADD)) {
+ mask |= Acl.ADD;
+ } else if (action.equalsIgnoreCase(REPLACE)) {
+ mask |= Acl.REPLACE;
+ } else if (action.equalsIgnoreCase(DELETE)) {
+ mask |= Acl.DELETE;
+ } else if (action.equalsIgnoreCase(EXEC)) {
+ mask |= Acl.EXEC;
+ } else
+ throw new IllegalArgumentException("Invalid action '" + action
+ + "'");
+ }
+
+ if (mask == 0)
+ throw new IllegalArgumentException("Action mask cannot be empty.");
+
+ return mask;
+ }
+
+ // generates the canonical string representation of the action list
+ private static String canonicalActions(int mask) {
+ StringBuffer sb = new StringBuffer();
+ addAction(sb, mask, Acl.ADD, ADD);
+ addAction(sb, mask, Acl.DELETE, DELETE);
+ addAction(sb, mask, Acl.EXEC, EXEC);
+ addAction(sb, mask, Acl.GET, GET);
+ addAction(sb, mask, Acl.REPLACE, REPLACE);
+ return sb.toString();
+ }
+
+ // if 'flag' appears in 'mask', appends the 'action' string to the contents
+ // of 'sb', separated by a comma if needed
+ private static void addAction(StringBuffer sb, int mask, int flag,
+ String action) {
+ if ((mask & flag) != 0) {
+ if (sb.length() > 0)
+ sb.append(',');
+ sb.append(action);
+ }
+ }
+
+ // used by DmtPermissionCollection to retrieve the action mask
+ int getMask() {
+ return mask;
+ }
+
+ // returns true if the path parameter of the given DmtPermission is
+ // implied by the path of this permission, i.e. this path is a prefix of the
+ // other path, but ends with a *, or the two path strings are equal
+ boolean impliesPath(DmtPermission p) {
+ return prefixPath ? p.path.startsWith(path) : !p.prefixPath
+ && p.path.equals(path);
+ }
+}
+
+/**
+ * Represents a homogeneous collection of DmtPermission objects.
+ */
+final class DmtPermissionCollection extends PermissionCollection {
+ private static final long serialVersionUID = -4172481774562012941L;
+
+ // OPTIMIZE keep a special flag for permissions of "*" path
+
+ private ArrayList perms;
+
+ /**
+ * Create an empty DmtPermissionCollection object.
+ */
+ public DmtPermissionCollection() {
+ perms = new ArrayList();
+ }
+
+ /**
+ * Adds a permission to the DmtPermissionCollection.
+ *
+ * @param permission the Permission object to add
+ * @exception IllegalArgumentException if the permission is not a
+ * DmtPermission
+ * @exception SecurityException if this DmtPermissionCollection object has
+ * been marked readonly
+ */
+ public void add(Permission permission) {
+ if (!(permission instanceof DmtPermission))
+ throw new IllegalArgumentException(
+ "Cannot add permission, invalid permission type: "
+ + permission);
+ if (isReadOnly())
+ throw new SecurityException(
+ "Cannot add permission, collection is marked read-only.");
+
+ // No need to synchronize because all adds are done sequentially
+ // before any implies() calls
+ perms.add(permission);
+ }
+
+ /**
+ * Check whether this set of permissions implies the permission specified in
+ * the parameter.
+ *
+ * @param permission the Permission object to compare
+ * @return true if the parameter permission is a proper subset of the
+ * permissions in the collection, false otherwise
+ */
+ public boolean implies(Permission permission) {
+ if (!(permission instanceof DmtPermission))
+ return false;
+
+ DmtPermission other = (DmtPermission) permission;
+
+ int required = other.getMask();
+ int available = 0;
+ int needed = required;
+
+ Iterator i = perms.iterator();
+ while (i.hasNext()) {
+ DmtPermission p = (DmtPermission) i.next();
+ if (((needed & p.getMask()) != 0) && p.impliesPath(other)) {
+ available |= p.getMask();
+ if ((available & required) == required)
+ return true;
+ needed = (required ^ available);
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns an enumeration of all the DmtPermission objects in the container.
+ * The returned value cannot be <code>null</code>.
+ *
+ * @return an enumeration of all the DmtPermission objects
+ */
+ public Enumeration elements() {
+ // Convert Iterator into Enumeration
+ return Collections.enumeration(perms);
+ }
+}
diff --git a/org.osgi.compendium/src/main/java/info/dmtree/security/DmtPrincipalPermission.java b/org.osgi.compendium/src/main/java/info/dmtree/security/DmtPrincipalPermission.java
new file mode 100644
index 0000000..e875055
--- /dev/null
+++ b/org.osgi.compendium/src/main/java/info/dmtree/security/DmtPrincipalPermission.java
@@ -0,0 +1,263 @@
+/*
+ * $Header: /cvshome/build/info.dmtree/src/info/dmtree/security/DmtPrincipalPermission.java,v 1.4 2006/07/12 21:21:52 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package info.dmtree.security;
+
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.util.*;
+
+/**
+ * Indicates the callers authority to create DMT sessions on behalf of a remote
+ * management server. Only protocol adapters communicating with management
+ * servers should be granted this permission.
+ * <p>
+ * <code>DmtPrincipalPermission</code> has a target string which controls the
+ * name of the principal on whose behalf the protocol adapter can act. A
+ * wildcard is allowed at the end of the target string, to allow using any
+ * principal name with the given prefix. The "*" target means the
+ * adapter can create a session in the name of any principal.
+ */
+public class DmtPrincipalPermission extends Permission {
+ private static final long serialVersionUID = 6388752177325038332L;
+
+ // specifies whether the target string had a wildcard at the end
+ private final boolean isPrefix;
+
+ // the target string without the wildcard (if there was one)
+ private final String principal;
+
+ /**
+ * Creates a new <code>DmtPrincipalPermission</code> object with its name
+ * set to the target string. Name must be non-null and non-empty.
+ *
+ * @param target the name of the principal, can end with <code>*</code> to
+ * match any principal with the given prefix
+ * @throws NullPointerException if <code>name</code> is <code>null</code>
+ * @throws IllegalArgumentException if <code>name</code> is empty
+ */
+ public DmtPrincipalPermission(String target) {
+ super(target);
+
+ if (target == null)
+ throw new NullPointerException(
+ "'target' parameter must not be null.");
+
+ if (target.equals(""))
+ throw new IllegalArgumentException(
+ "'target' parameter must not be empty.");
+
+ isPrefix = target.endsWith("*");
+ if (isPrefix)
+ principal = target.substring(0, target.length() - 1);
+ else
+ principal = target;
+ }
+
+ /**
+ * Creates a new <code>DmtPrincipalPermission</code> object using the
+ * 'canonical' two argument constructor. In this version this class does not
+ * define any actions, the second argument of this constructor must be "*"
+ * so that this class can later be extended in a backward compatible way.
+ *
+ * @param target the name of the principal, can end with <code>*</code> to
+ * match any principal with the given prefix
+ * @param actions no actions defined, must be "*" for forward compatibility
+ * @throws NullPointerException if <code>name</code> or
+ * <code>actions</code> is <code>null</code>
+ * @throws IllegalArgumentException if <code>name</code> is empty or
+ * <code>actions</code> is not "*"
+ */
+ public DmtPrincipalPermission(String target, String actions) {
+ this(target);
+
+ if (actions == null)
+ throw new NullPointerException(
+ "'actions' parameter must not be null.");
+
+ if (!actions.equals("*"))
+ throw new IllegalArgumentException(
+ "'actions' parameter must be \"*\".");
+ }
+
+ /**
+ * Checks whether the given object is equal to this DmtPrincipalPermission
+ * instance. Two DmtPrincipalPermission instances are equal if they have the
+ * same target string.
+ *
+ * @param obj the object to compare to this DmtPrincipalPermission instance
+ * @return <code>true</code> if the parameter represents the same
+ * permissions as this instance
+ */
+ public boolean equals(Object obj) {
+ if (obj == this)
+ return true;
+
+ if (!(obj instanceof DmtPrincipalPermission))
+ return false;
+
+ DmtPrincipalPermission other = (DmtPrincipalPermission) obj;
+
+ return isPrefix == other.isPrefix && principal.equals(other.principal);
+ }
+
+ /**
+ * Returns the action list (always <code>*</code> in the current version).
+ *
+ * @return the action string "*"
+ */
+ public String getActions() {
+ return "*";
+ }
+
+ /**
+ * Returns the hash code for this permission object. If two
+ * DmtPrincipalPermission objects are equal according to the {@link #equals}
+ * method, then calling this method on each of the two
+ * DmtPrincipalPermission objects must produce the same integer result.
+ *
+ * @return hash code for this permission object
+ */
+ public int hashCode() {
+ return new Boolean(isPrefix).hashCode() ^ principal.hashCode();
+ }
+
+ /**
+ * Checks if this DmtPrincipalPermission object implies the specified
+ * permission. Another DmtPrincipalPermission instance is implied by this
+ * permission either if the target strings are identical, or if this target
+ * can be made identical to the other target by replacing a trailing
+ * "*" with any string.
+ *
+ * @param p the permission to check for implication
+ * @return true if this DmtPrincipalPermission instance implies the
+ * specified permission
+ */
+ public boolean implies(Permission p) {
+ if (!(p instanceof DmtPrincipalPermission))
+ return false;
+
+ DmtPrincipalPermission other = (DmtPrincipalPermission) p;
+
+ return impliesPrincipal(other);
+ }
+
+ /**
+ * Returns a new PermissionCollection object for storing
+ * DmtPrincipalPermission objects.
+ *
+ * @return the new PermissionCollection
+ */
+ public PermissionCollection newPermissionCollection() {
+ return new DmtPrincipalPermissionCollection();
+ }
+
+ /*
+ * Returns true if the principal parameter of the given
+ * DmtPrincipalPermission is implied by the principal of this permission,
+ * i.e. this principal is a prefix of the other principal but ends with a *,
+ * or the two principal strings are equal.
+ */
+ boolean impliesPrincipal(DmtPrincipalPermission p) {
+ return isPrefix ? p.principal.startsWith(principal) : !p.isPrefix
+ && p.principal.equals(principal);
+ }
+}
+
+/**
+ * Represents a homogeneous collection of DmtPrincipalPermission objects.
+ */
+final class DmtPrincipalPermissionCollection extends PermissionCollection {
+ private static final long serialVersionUID = -6692103535775802684L;
+
+ private ArrayList perms;
+
+ /**
+ * Create an empty DmtPrincipalPermissionCollection object.
+ */
+ public DmtPrincipalPermissionCollection() {
+ perms = new ArrayList();
+ }
+
+ /**
+ * Adds a permission to the DmtPrincipalPermissionCollection.
+ *
+ * @param permission the Permission object to add
+ * @exception IllegalArgumentException if the permission is not a
+ * DmtPrincipalPermission
+ * @exception SecurityException if this DmtPrincipalPermissionCollection
+ * object has been marked readonly
+ */
+ public void add(Permission permission) {
+ if (!(permission instanceof DmtPrincipalPermission))
+ throw new IllegalArgumentException(
+ "Cannot add permission, invalid permission type: "
+ + permission);
+ if (isReadOnly())
+ throw new SecurityException(
+ "Cannot add permission, collection is marked read-only.");
+
+ // only add new permission if it is not already implied by the
+ // permissions in the collection
+ if (!implies(permission)) {
+ // remove all permissions that are implied by the new one
+ Iterator i = perms.iterator();
+ while (i.hasNext())
+ if (permission.implies((DmtPrincipalPermission) i.next()))
+ i.remove();
+
+ // no need to synchronize because all adds are done sequentially
+ // before any implies() calls
+ perms.add(permission);
+
+ }
+ }
+
+ /**
+ * Check whether this set of permissions implies the permission specified in
+ * the parameter.
+ *
+ * @param permission the Permission object to compare
+ * @return true if the parameter permission is a proper subset of the
+ * permissions in the collection, false otherwise
+ */
+ public boolean implies(Permission permission) {
+ if (!(permission instanceof DmtPrincipalPermission))
+ return false;
+
+ DmtPrincipalPermission other = (DmtPrincipalPermission) permission;
+
+ Iterator i = perms.iterator();
+ while (i.hasNext())
+ if (((DmtPrincipalPermission) i.next()).impliesPrincipal(other))
+ return true;
+
+ return false;
+ }
+
+ /**
+ * Returns an enumeration of all the DmtPrincipalPermission objects in the
+ * container. The returned value cannot be <code>null</code>.
+ *
+ * @return an enumeration of all the DmtPrincipalPermission objects
+ */
+ public Enumeration elements() {
+ // Convert Iterator into Enumeration
+ return Collections.enumeration(perms);
+ }
+}
diff --git a/org.osgi.compendium/src/main/java/info/dmtree/spi/DataPlugin.java b/org.osgi.compendium/src/main/java/info/dmtree/spi/DataPlugin.java
new file mode 100644
index 0000000..ab3eea4
--- /dev/null
+++ b/org.osgi.compendium/src/main/java/info/dmtree/spi/DataPlugin.java
@@ -0,0 +1,137 @@
+/*
+ * $Header: /cvshome/build/info.dmtree/src/info/dmtree/spi/DataPlugin.java,v 1.4 2006/06/16 16:31:59 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package info.dmtree.spi;
+
+import info.dmtree.DmtException;
+import info.dmtree.DmtSession;
+
+/**
+ * An implementation of this interface takes the responsibility of handling data
+ * requests in a subtree of the DMT.
+ * <p>
+ * In an OSGi environment such implementations should be registered at the OSGi
+ * service registry specifying the list of root node URIs in a
+ * <code>String</code> array in the <code>dataRootURIs</code> registration
+ * parameter.
+ * <p>
+ * When the first reference in a session is made to a node handled by this
+ * plugin, the DmtAdmin calls one of the <code>open...</code> methods to
+ * retrieve a plugin session object for processing the request. The called
+ * method depends on the lock type of the current session. In case of
+ * {@link #openReadWriteSession(String[], DmtSession)} and
+ * {@link #openAtomicSession(String[], DmtSession)}, the plugin may return
+ * <code>null</code> to indicate that the specified lock type is not
+ * supported. In this case the DmtAdmin may call
+ * {@link #openReadOnlySession(String[], DmtSession)} to start a read-only
+ * plugin session, which can be used as long as there are no write operations on
+ * the nodes handled by this plugin.
+ * <p>
+ * The <code>sessionRoot</code> parameter of each method is a String array
+ * containing the segments of the URI pointing to the root of the session. This
+ * is an absolute path, so the first segment is always ".". Special
+ * characters appear escaped in the segments.
+ * <p>
+ */
+public interface DataPlugin {
+
+ /**
+ * This method is called to signal the start of a read-only session when the
+ * first reference is made within a <code>DmtSession</code> to a node
+ * which is handled by this plugin. Session information is given as it is
+ * needed for sending alerts back from the plugin.
+ * <p>
+ * The plugin can assume that there are no writing sessions open on any
+ * subtree that has any overlap with the subtree of this session.
+ *
+ * @param sessionRoot the path to the subtree which is accessed in the
+ * current session, must not be <code>null</code>
+ * @param session the session from which this plugin instance is accessed,
+ * must not be <code>null</code>
+ * @return a plugin session capable of executing read operations
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>NODE_NOT_FOUND</code> if <code>sessionRoot</code>
+ * points to a non-existing node
+ * <li><code>COMMAND_FAILED</code> if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws SecurityException if some underlying operation failed because of
+ * lack of permissions
+ */
+ ReadableDataSession openReadOnlySession(String[] sessionRoot,
+ DmtSession session) throws DmtException;
+
+ /**
+ * This method is called to signal the start of a non-atomic read-write
+ * session when the first reference is made within a <code>DmtSession</code>
+ * to a node which is handled by this plugin. Session information is given
+ * as it is needed for sending alerts back from the plugin.
+ * <p>
+ * The plugin can assume that there are no other sessions open on any
+ * subtree that has any overlap with the subtree of this session.
+ *
+ * @param sessionRoot the path to the subtree which is locked in the current
+ * session, must not be <code>null</code>
+ * @param session the session from which this plugin instance is accessed,
+ * must not be <code>null</code>
+ * @return a plugin session capable of executing read-write operations, or
+ * <code>null</code> if the plugin does not support non-atomic
+ * read-write sessions
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>NODE_NOT_FOUND</code> if <code>sessionRoot</code>
+ * points to a non-existing node
+ * <li><code>COMMAND_FAILED</code> if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws SecurityException if some underlying operation failed because of
+ * lack of permissions
+ */
+ ReadWriteDataSession openReadWriteSession(String[] sessionRoot,
+ DmtSession session) throws DmtException;
+
+ /**
+ * This method is called to signal the start of an atomic read-write session
+ * when the first reference is made within a <code>DmtSession</code> to a
+ * node which is handled by this plugin. Session information is given as it
+ * is needed for sending alerts back from the plugin.
+ * <p>
+ * The plugin can assume that there are no other sessions open on any
+ * subtree that has any overlap with the subtree of this session.
+ *
+ * @param sessionRoot the path to the subtree which is locked in the current
+ * session, must not be <code>null</code>
+ * @param session the session from which this plugin instance is accessed,
+ * must not be <code>null</code>
+ * @return a plugin session capable of executing read-write operations in an
+ * atomic block, or <code>null</code> if the plugin does not
+ * support atomic read-write sessions
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>NODE_NOT_FOUND</code> if <code>sessionRoot</code>
+ * points to a non-existing node
+ * <li><code>COMMAND_FAILED</code> if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws SecurityException if some underlying operation failed because of
+ * lack of permissions
+ */
+ TransactionalDataSession openAtomicSession(String[] sessionRoot,
+ DmtSession session) throws DmtException;
+}
diff --git a/org.osgi.compendium/src/main/java/info/dmtree/spi/ExecPlugin.java b/org.osgi.compendium/src/main/java/info/dmtree/spi/ExecPlugin.java
new file mode 100644
index 0000000..b2fe0f3
--- /dev/null
+++ b/org.osgi.compendium/src/main/java/info/dmtree/spi/ExecPlugin.java
@@ -0,0 +1,74 @@
+/*
+ * $Header: /cvshome/build/info.dmtree/src/info/dmtree/spi/ExecPlugin.java,v 1.3 2006/06/16 16:31:59 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package info.dmtree.spi;
+
+import info.dmtree.DmtException;
+import info.dmtree.DmtSession;
+
+/**
+ * An implementation of this interface takes the responsibility of handling node
+ * execute requests requests in a subtree of the DMT.
+ * <p>
+ * In an OSGi environment such implementations should be registered at the OSGi
+ * service registry specifying the list of root node URIs in a
+ * <code>String</code> array in the <code>execRootURIs</code> registration
+ * parameter.
+ */
+public interface ExecPlugin {
+
+ /**
+ * Execute the given node with the given data. This operation corresponds to
+ * the EXEC command in OMA DM.
+ * <p>
+ * The semantics of an execute operation and the data parameter it takes
+ * depends on the definition of the managed object on which the command is
+ * issued. Session information is given as it is needed for sending alerts
+ * back from the plugin. If a correlation ID is specified, it should be used
+ * as the <code>correlator</code> parameter for alerts sent in response to
+ * this execute operation.
+ * <p>
+ * The <code>nodePath</code> parameter contains an array of path segments
+ * identifying the node to be executed in the subtree of this plugin. This
+ * is an absolute path, so the first segment is always ".".
+ * Special characters appear escaped in the segments.
+ *
+ * @param session a reference to the session in which the operation was
+ * issued, must not be <code>null</code>
+ * @param nodePath the absolute path of the node to be executed, must not be
+ * <code>null</code>
+ * @param correlator an identifier to associate this operation with any
+ * alerts sent in response to it, can be <code>null</code>
+ * @param data the parameter of the execute operation, can be
+ * <code>null</code>
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>NODE_NOT_FOUND</code> if the node does not exist and
+ * the plugin does not allow executing unexisting nodes
+ * <li><code>METADATA_MISMATCH</code> if the command failed
+ * because of meta-data restrictions
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @see DmtSession#execute(String, String)
+ * @see DmtSession#execute(String, String, String)
+ */
+ void execute(DmtSession session, String[] nodePath, String correlator,
+ String data) throws DmtException;
+}
diff --git a/org.osgi.compendium/src/main/java/info/dmtree/spi/ReadWriteDataSession.java b/org.osgi.compendium/src/main/java/info/dmtree/spi/ReadWriteDataSession.java
new file mode 100644
index 0000000..54e444b
--- /dev/null
+++ b/org.osgi.compendium/src/main/java/info/dmtree/spi/ReadWriteDataSession.java
@@ -0,0 +1,297 @@
+/*
+ * $Header: /cvshome/build/info.dmtree/src/info/dmtree/spi/ReadWriteDataSession.java,v 1.4 2006/07/12 21:21:52 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package info.dmtree.spi;
+
+import info.dmtree.*;
+
+/**
+ * Provides non-atomic read-write access to the part of the tree handled by the
+ * plugin that created this session.
+ * <p>
+ * The <code>nodePath</code> parameters appearing in this interface always
+ * contain an array of path segments identifying a node in the subtree of this
+ * plugin. This parameter contains an absolute path, so the first segment is
+ * always ".". Special characters appear escaped in the segments.
+ * <p>
+ * <strong>Error handling</strong>
+ * <p>
+ * When a tree manipulation command is called on the DmtAdmin service, it must
+ * perform an extensive set of checks on the parameters and the authority of the
+ * caller before delegating the call to a plugin. Therefore plugins can take
+ * certain circumstances for granted: that the path is valid and is within the
+ * subtree of the plugin and the session, the command can be applied to the
+ * given node (e.g. the target of <code>setNodeValue</code> is a leaf node),
+ * etc. All errors described by the error codes {@link DmtException#INVALID_URI},
+ * {@link DmtException#URI_TOO_LONG}, {@link DmtException#PERMISSION_DENIED},
+ * {@link DmtException#COMMAND_NOT_ALLOWED} and
+ * {@link DmtException#TRANSACTION_ERROR} are fully filtered out before control
+ * reaches the plugin.
+ * <p>
+ * If the plugin provides meta-data for a node, the DmtAdmin service must also
+ * check the constraints specified by it, as described in {@link MetaNode}. If
+ * the plugin does not provide meta-data, it must perform the necessary checks
+ * for itself and use the {@link DmtException#METADATA_MISMATCH} error code to
+ * indicate such discrepancies.
+ * <p>
+ * The DmtAdmin also ensures that the targeted nodes exist before calling the
+ * plugin (or that they do not exist, in case of node creation). However, some
+ * small amount of time elapses between the check and the call, so in case of
+ * plugins where the node structure can change independantly from the DMT, the
+ * target node might appear/disappear in that time. For example, a whole subtree
+ * can disappear when a Monitorable application is unregistered, which might
+ * happen in the middle of a DMT session accessing it. Plugins managing such
+ * nodes always need to check the existance or non-existance of nodes and throw
+ * {@link DmtException#NODE_NOT_FOUND} or
+ * {@link DmtException#NODE_ALREADY_EXISTS} as necessary, but for more static
+ * subtrees there is no need for the plugin to use these error codes.
+ * <p>
+ * The plugin can use the remaining error codes as needed. If an error does not
+ * fit into any other category, the {@link DmtException#COMMAND_FAILED} code
+ * should be used.
+ */
+public interface ReadWriteDataSession extends ReadableDataSession {
+
+ /**
+ * Create a copy of a node or a whole subtree. Beside the structure and
+ * values of the nodes, most properties managed by the plugin must also be
+ * copied, with the exception of the Timestamp and Version properties.
+ *
+ * @param nodePath an absolute path specifying the node or the root of a
+ * subtree to be copied
+ * @param newNodePath the absolute path of the new node or root of a subtree
+ * @param recursive <code>false</code> if only a single node is copied,
+ * <code>true</code> if the whole subtree is copied
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodePath</code>
+ * points to a non-existing node, or if <code>newNodePath</code>
+ * points to a node that cannot exist in the tree
+ * <li><code>NODE_ALREADY_EXISTS</code> if
+ * <code>newNodePath</code> points to a node that already exists
+ * <li><code>METADATA_MISMATCH</code> if the node could not be
+ * copied because of meta-data restrictions
+ * <li><code>FEATURE_NOT_SUPPORTED</code> if the copy operation
+ * is not supported by the plugin
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation
+ * @see DmtSession#copy(String, String, boolean)
+ */
+ void copy(String[] nodePath, String[] newNodePath, boolean recursive)
+ throws DmtException;
+
+ /**
+ * Create an interior node with a given type. The type of interior node, if
+ * specified, is a URI identifying a DDF document.
+ *
+ * @param nodePath the absolute path of the node to create
+ * @param type the type URI of the interior node, can be <code>null</code>
+ * if no node type is defined
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodePath</code>
+ * points to a node that cannot exist in the tree
+ * <li><code>NODE_ALREADY_EXISTS</code> if <code>nodeUri</code>
+ * points to a node that already exists
+ * <li><code>METADATA_MISMATCH</code> if the node could not be
+ * created because of meta-data restrictions
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation
+ * @see DmtSession#createInteriorNode(String)
+ * @see DmtSession#createInteriorNode(String, String)
+ */
+ void createInteriorNode(String[] nodePath, String type) throws DmtException;
+
+ /**
+ * Create a leaf node with a given value and MIME type. If the specified
+ * value or MIME type is <code>null</code>, their default values must be
+ * taken.
+ *
+ * @param nodePath the absolute path of the node to create
+ * @param value the value to be given to the new node, can be
+ * <code>null</code>
+ * @param mimeType the MIME type to be given to the new node, can be
+ * <code>null</code>
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodePath</code>
+ * points to a node that cannot exist in the tree
+ * <li><code>NODE_ALREADY_EXISTS</code> if <code>nodePath</code>
+ * points to a node that already exists
+ * <li><code>METADATA_MISMATCH</code> if the node could not be
+ * created because of meta-data restrictions
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation
+ * @see DmtSession#createLeafNode(String)
+ * @see DmtSession#createLeafNode(String, DmtData)
+ * @see DmtSession#createLeafNode(String, DmtData, String)
+ */
+ void createLeafNode(String[] nodePath, DmtData value, String mimeType)
+ throws DmtException;
+
+ /**
+ * Delete the given node. Deleting interior nodes is recursive, the whole
+ * subtree under the given node is deleted.
+ *
+ * @param nodePath the absolute path of the node to delete
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodePath</code>
+ * points to a non-existing node
+ * <li><code>METADATA_MISMATCH</code> if the node could not be
+ * deleted because of meta-data restrictions
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation
+ * @see DmtSession#deleteNode(String)
+ */
+ void deleteNode(String[] nodePath) throws DmtException;
+
+ /**
+ * Rename a node. This operation only changes the name of the node (updating
+ * the timestamp and version properties if they are supported), the value
+ * and the other properties are not changed. The new name of the node must
+ * be provided, the new path is constructed from the base of the old path
+ * and the given name.
+ *
+ * @param nodePath the absolute path of the node to rename
+ * @param newName the new name property of the node
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodePath</code>
+ * points to a non-existing node, or if the new node is not defined
+ * in the tree
+ * <li><code>NODE_ALREADY_EXISTS</code> if there already exists a
+ * sibling of <code>nodePath</code> with the name
+ * <code>newName</code>
+ * <li><code>METADATA_MISMATCH</code> if the node could not be
+ * renamed because of meta-data restrictions
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation
+ * @see DmtSession#renameNode(String, String)
+ */
+ void renameNode(String[] nodePath, String newName) throws DmtException;
+
+ /**
+ * Set the title property of a node. The length of the title is guaranteed
+ * not to exceed the limit of 255 bytes in UTF-8 encoding.
+ *
+ * @param nodePath the absolute path of the node
+ * @param title the title text of the node, can be <code>null</code>
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodePath</code>
+ * points to a non-existing node
+ * <li><code>METADATA_MISMATCH</code> if the title could not be
+ * set because of meta-data restrictions
+ * <li><code>FEATURE_NOT_SUPPORTED</code> if the Title property
+ * is not supported by the plugin
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation
+ * @see DmtSession#setNodeTitle(String, String)
+ */
+ void setNodeTitle(String[] nodePath, String title) throws DmtException;
+
+ /**
+ * Set the type of a node. The type of leaf node is the MIME type of the
+ * data it contains. The type of an interior node is a URI identifying a DDF
+ * document.
+ * <p>
+ * For interior nodes, the <code>null</code> type should remove the
+ * reference (if any) to a DDF document overriding the tree structure
+ * defined by the ancestors. For leaf nodes, it requests that the default
+ * MIME type is used for the given node.
+ *
+ * @param nodePath the absolute path of the node
+ * @param type the type of the node, can be <code>null</code>
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodePath</code>
+ * points to a non-existing node
+ * <li><code>METADATA_MISMATCH</code> if the type could not be
+ * set because of meta-data restrictions
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation
+ * @see DmtSession#setNodeType(String, String)
+ */
+ void setNodeType(String[] nodePath, String type) throws DmtException;
+
+ /**
+ * Set the value of a leaf or interior node. The format of the node is
+ * contained in the <code>DmtData</code> object. For interior nodes, the
+ * format is <code>FORMAT_NODE</code>, while for leaf nodes this format is
+ * never used.
+ * <p>
+ * If the specified value is <code>null</code>, the default value must be
+ * taken; if there is no default value, a <code>DmtException</code> with
+ * error code <code>METADATA_MISMATCH</code> must be thrown.
+ *
+ * @param nodePath the absolute path of the node
+ * @param data the data to be set, can be <code>null</code>
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodePath</code>
+ * points to a non-existing node
+ * <li><code>METADATA_MISMATCH</code> if the value could not be
+ * set because of meta-data restrictions
+ * <li><code>FEATURE_NOT_SUPPORTED</code> if the specified node is
+ * an interior node and does not support Java object values
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation
+ * @see DmtSession#setNodeValue(String, DmtData)
+ */
+ void setNodeValue(String[] nodePath, DmtData data) throws DmtException;
+}
diff --git a/org.osgi.compendium/src/main/java/info/dmtree/spi/ReadableDataSession.java b/org.osgi.compendium/src/main/java/info/dmtree/spi/ReadableDataSession.java
new file mode 100644
index 0000000..7897bad
--- /dev/null
+++ b/org.osgi.compendium/src/main/java/info/dmtree/spi/ReadableDataSession.java
@@ -0,0 +1,360 @@
+/*
+ * $Header: /cvshome/build/info.dmtree/src/info/dmtree/spi/ReadableDataSession.java,v 1.4 2006/07/12 21:21:52 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package info.dmtree.spi;
+
+import info.dmtree.*;
+
+import java.util.Date;
+
+/**
+ * Provides read-only access to the part of the tree handled by the plugin that
+ * created this session.
+ * <p>
+ * Since the {@link ReadWriteDataSession} and {@link TransactionalDataSession}
+ * interfaces inherit from this interface, some of the method descriptions do
+ * not apply for an instance that is only a <code>ReadableDataSession</code>.
+ * For example, the {@link #close} method description also contains information
+ * about its behaviour when invoked as part of a transactional session.
+ * <p>
+ * The <code>nodePath</code> parameters appearing in this interface always
+ * contain an array of path segments identifying a node in the subtree of this
+ * plugin. This parameter contains an absolute path, so the first segment is
+ * always ".". Special characters appear escaped in the segments.
+ * <p>
+ * <strong>Error handling</strong>
+ * <p>
+ * When a tree access command is called on the DmtAdmin service, it must
+ * perform an extensive set of checks on the parameters and the authority of the
+ * caller before delegating the call to a plugin. Therefore plugins can take
+ * certain circumstances for granted: that the path is valid and is within the
+ * subtree of the plugin and the session, the command can be applied to the
+ * given node (e.g. the target of <code>getChildNodeNames</code> is an
+ * interior node), etc. All errors described by the error codes
+ * {@link DmtException#INVALID_URI}, {@link DmtException#URI_TOO_LONG},
+ * {@link DmtException#PERMISSION_DENIED},
+ * {@link DmtException#COMMAND_NOT_ALLOWED} and
+ * {@link DmtException#TRANSACTION_ERROR} are fully filtered out before control
+ * reaches the plugin.
+ * <p>
+ * If the plugin provides meta-data for a node, the DmtAdmin service must also
+ * check the constraints specified by it, as described in {@link MetaNode}. If
+ * the plugin does not provide meta-data, it must perform the necessary checks
+ * for itself and use the {@link DmtException#METADATA_MISMATCH} error code to
+ * indicate such discrepancies.
+ * <p>
+ * The DmtAdmin also ensures that the targeted nodes exist before calling the
+ * plugin (except, of course, before the <code>isNodeUri</code> call).
+ * However, some small amount of time elapses between the check and the call, so
+ * in case of plugins where the node structure can change independantly from the
+ * DMT, the target node might disappear in that time. For example, a whole
+ * subtree can disappear when a Monitorable application is unregistered, which
+ * might happen in the middle of a DMT session accessing it. Plugins managing
+ * such nodes always need to check whether they still exist and throw
+ * {@link DmtException#NODE_NOT_FOUND} as necessary, but for more static
+ * subtrees there is no need for the plugin to use this error code.
+ * <p>
+ * The plugin can use the remaining error codes as needed. If an error does not
+ * fit into any other category, the {@link DmtException#COMMAND_FAILED} code
+ * should be used.
+ */
+public interface ReadableDataSession {
+ /**
+ * Notifies the plugin that the given node has changed outside the scope of
+ * the plugin, therefore the Version and Timestamp properties must be
+ * updated (if supported). This method is needed because the ACL property of
+ * a node is managed by the DmtAdmin instead of the plugin. The DmtAdmin
+ * must call this method whenever the ACL property of a node changes.
+ *
+ * @param nodePath the absolute path of the node that has changed
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodePath</code>
+ * points to a non-existing node
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ */
+ void nodeChanged(String[] nodePath) throws DmtException;
+
+ /**
+ * Closes a session. This method is always called when the session ends for
+ * any reason: if the session is closed, if a fatal error occurs in any
+ * method, or if any error occurs during commit or rollback. In case the
+ * session was invalidated due to an exception during commit or rollback, it
+ * is guaranteed that no methods are called on the plugin until it is
+ * closed. In case the session was invalidated due to a fatal exception in
+ * one of the tree manipulation methods, only the rollback method is called
+ * before this (and only in atomic sessions).
+ * <p>
+ * This method should not perform any data manipulation, only cleanup
+ * operations. In non-atomic read-write sessions the data manipulation
+ * should be done instantly during each tree operation, while in atomic
+ * sessions the <code>DmtAdmin</code> always calls
+ * {@link TransactionalDataSession#commit} automatically before the session
+ * is actually closed.
+ *
+ * @throws DmtException with the error code <code>COMMAND_FAILED</code> if
+ * the plugin failed to close for any reason
+ */
+ void close() throws DmtException;
+
+ /**
+ * Get the list of children names of a node. The returned array contains the
+ * names - not the URIs - of the immediate children nodes of the given node.
+ * The returned child names must be mangled ({@link info.dmtree.Uri#mangle}).
+ * The returned array may contain <code>null</code> entries, but these are
+ * removed by the DmtAdmin before returning it to the client.
+ *
+ * @param nodePath the absolute path of the node
+ * @return the list of child node names as a string array or an empty string
+ * array if the node has no children
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodePath</code>
+ * points to a non-existing node
+ * <li><code>METADATA_MISMATCH</code> if the information could
+ * not be retrieved because of meta-data restrictions
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation
+ */
+ String[] getChildNodeNames(String[] nodePath) throws DmtException;
+
+ /**
+ * Get the meta data which describes a given node. Meta data can be only
+ * inspected, it can not be changed.
+ * <p>
+ * Meta data support by plugins is an optional feature. It can be used, for
+ * example, when a data plugin is implemented on top of a data store or
+ * another API that has their own metadata, such as a relational database,
+ * in order to avoid metadata duplication and inconsistency. The meta data
+ * specific to the plugin returned by this method is complemented by meta
+ * data from the DmtAdmin before returning it to the client. If there are
+ * differences in the meta data elements known by the plugin and the
+ * <code>DmtAdmin</code> then the plugin specific elements take
+ * precedence.
+ * <p>
+ * Note, that a node does not have to exist for having meta-data associated
+ * with it. This method may provide meta-data for any node that can possibly
+ * exist in the tree (any node defined by the Management Object provided by
+ * the plugin). For nodes that are not defined, a <code>DmtException</code>
+ * may be thrown with the <code>NODE_NOT_FOUND</code> error code. To allow
+ * easier implementation of plugins that do not provide meta-data, it is
+ * allowed to return <code>null</code> for any node, regardless of whether
+ * it is defined or not.
+ *
+ * @param nodePath the absolute path of the node
+ * @return a MetaNode which describes meta data information, can be
+ * <code>null</code> if there is no meta data available for the
+ * given node
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodeUri</code>
+ * points to a node that is not defined in the tree (see above)
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation
+ */
+ MetaNode getMetaNode(String[] nodePath) throws DmtException;
+
+ /**
+ * Get the size of the data in a leaf node. The value to return depends on
+ * the format of the data in the node, see the description of the
+ * {@link DmtData#getSize()} method for the definition of node size for each
+ * format.
+ *
+ * @param nodePath the absolute path of the leaf node
+ * @return the size of the data in the node
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodePath</code>
+ * points to a non-existing node
+ * <li><code>METADATA_MISMATCH</code> if the information could
+ * not be retrieved because of meta-data restrictions
+ * <li><code>FEATURE_NOT_SUPPORTED</code> if the Size property is
+ * not supported by the plugin
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation
+ * @see DmtData#getSize
+ */
+ int getNodeSize(String[] nodePath) throws DmtException;
+
+ /**
+ * Get the timestamp when the node was last modified.
+ *
+ * @param nodePath the absolute path of the node
+ * @return the timestamp of the last modification
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodePath</code>
+ * points to a non-existing node
+ * <li><code>METADATA_MISMATCH</code> if the information could
+ * not be retrieved because of meta-data restrictions
+ * <li><code>FEATURE_NOT_SUPPORTED</code> if the Timestamp
+ * property is not supported by the plugin
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation
+ */
+ Date getNodeTimestamp(String[] nodePath) throws DmtException;
+
+ /**
+ * Get the title of a node. There might be no title property set for a node.
+ *
+ * @param nodePath the absolute path of the node
+ * @return the title of the node, or <code>null</code> if the node has no
+ * title
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodePath</code>
+ * points to a non-existing node
+ * <li><code>METADATA_MISMATCH</code> if the information could
+ * not be retrieved because of meta-data restrictions
+ * <li><code>FEATURE_NOT_SUPPORTED</code> if the Title property
+ * is not supported by the plugin
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation
+ */
+ String getNodeTitle(String[] nodePath) throws DmtException;
+
+ /**
+ * Get the type of a node. The type of leaf node is the MIME type of the
+ * data it contains. The type of an interior node is a URI identifying a DDF
+ * document; a <code>null</code> type means that there is no DDF document
+ * overriding the tree structure defined by the ancestors.
+ *
+ * @param nodePath the absolute path of the node
+ * @return the type of the node, can be <code>null</code>
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodePath</code>
+ * points to a non-existing node
+ * <li><code>METADATA_MISMATCH</code> if the information could
+ * not be retrieved because of meta-data restrictions
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation
+ */
+ String getNodeType(String[] nodePath) throws DmtException;
+
+ /**
+ * Check whether the specified path corresponds to a valid node in the DMT.
+ *
+ * @param nodePath the absolute path to check
+ * @return true if the given node exists in the DMT
+ */
+ boolean isNodeUri(String[] nodePath);
+
+ /**
+ * Tells whether a node is a leaf or an interior node of the DMT.
+ *
+ * @param nodePath the absolute path of the node
+ * @return true if the given node is a leaf node
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodePath</code>
+ * points to a non-existing node
+ * <li><code>METADATA_MISMATCH</code> if the information could
+ * not be retrieved because of meta-data restrictions
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation
+ */
+ boolean isLeafNode(String[] nodePath) throws DmtException;
+
+ /**
+ * Get the data contained in a leaf or interior node.
+ *
+ * @param nodePath the absolute path of the node to retrieve
+ * @return the data of the leaf node, must not be <code>null</code>
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodePath</code>
+ * points to a non-existing node
+ * <li><code>METADATA_MISMATCH</code> if the information could
+ * not be retrieved because of meta-data restrictions
+ * <li><code>FEATURE_NOT_SUPPORTED</code> if the specified node is
+ * an interior node and does not support Java object values
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation
+ */
+ DmtData getNodeValue(String[] nodePath) throws DmtException;
+
+ /**
+ * Get the version of a node. The version can not be set, it is calculated
+ * automatically by the device. It is incremented modulo 0x10000 at every
+ * modification of the value or any other property of the node, for both
+ * leaf and interior nodes. When a node is created the initial value is 0.
+ *
+ * @param nodePath the absolute path of the node
+ * @return the version of the node
+ * @throws DmtException with the following possible error codes:
+ * <ul>
+ * <li><code>NODE_NOT_FOUND</code> if <code>nodePath</code>
+ * points to a non-existing node
+ * <li><code>METADATA_MISMATCH</code> if the information could
+ * not be retrieved because of meta-data restrictions
+ * <li><code>FEATURE_NOT_SUPPORTED</code> if the Version property
+ * is not supported by the plugin
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation
+ */
+ int getNodeVersion(String[] nodePath) throws DmtException;
+}
diff --git a/org.osgi.compendium/src/main/java/info/dmtree/spi/TransactionalDataSession.java b/org.osgi.compendium/src/main/java/info/dmtree/spi/TransactionalDataSession.java
new file mode 100644
index 0000000..4ddb694
--- /dev/null
+++ b/org.osgi.compendium/src/main/java/info/dmtree/spi/TransactionalDataSession.java
@@ -0,0 +1,77 @@
+/*
+ * $Header: /cvshome/build/info.dmtree/src/info/dmtree/spi/TransactionalDataSession.java,v 1.2 2006/06/16 16:31:59 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package info.dmtree.spi;
+
+import info.dmtree.DmtException;
+
+/**
+ * Provides atomic read-write access to the part of the tree handled by the
+ * plugin that created this session.
+ */
+public interface TransactionalDataSession extends ReadWriteDataSession {
+
+ /**
+ * Commits a series of DMT operations issued in the current atomic session
+ * since the last transaction boundary. Transaction boundaries are the
+ * creation of this object that starts the session, and all subsequent
+ * {@link #commit} and {@link #rollback} calls.
+ * <p>
+ * This method can fail even if all operations were successful. This can
+ * happen due to some multi-node semantic constraints defined by a specific
+ * implementation. For example, node A can be required to always have
+ * children A/B, A/C and A/D. If this condition is broken when
+ * <code>commit()</code> is executed, the method will fail, and throw a
+ * <code>METADATA_MISMATCH</code> exception.
+ * <p>
+ * In many cases the tree is not the only way to manage a given part of the
+ * system. It may happen that while modifying some nodes in an atomic
+ * session, the underlying settings are modified parallelly outside the
+ * scope of the DMT. If this is detected during commit, an exception with
+ * the code <code>CONCURRENT_ACCESS</code> is thrown.
+ *
+ * @throws DmtException with the following possible error codes
+ * <ul>
+ * <li><code>METADATA_MISMATCH</code> if the operation failed
+ * because of meta-data restrictions
+ * <li><code>CONCURRENT_ACCESS</code> if it is detected that some
+ * modification has been made outside the scope of the DMT to the
+ * nodes affected in the session's operations
+ * <li><code>DATA_STORE_FAILURE</code> if an error occurred while
+ * accessing the data store
+ * <li><code>COMMAND_FAILED</code> if some unspecified error is
+ * encountered while attempting to complete the command
+ * </ul>
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation
+ */
+ void commit() throws DmtException;
+
+ /**
+ * Rolls back a series of DMT operations issued in the current atomic
+ * session since the last transaction boundary. Transaction boundaries are
+ * the creation of this object that starts the session, and all subsequent
+ * {@link #commit} and {@link #rollback} calls.
+ *
+ * @throws DmtException with the error code <code>ROLLBACK_FAILED</code>
+ * in case the rollback did not succeed
+ * @throws SecurityException if the caller does not have the necessary
+ * permissions to execute the underlying management operation
+ */
+ void rollback() throws DmtException;
+}