package org.argeo.cms.swt.acr;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-
import javax.xml.namespace.QName;
import org.argeo.api.acr.Content;
-import org.argeo.cms.acr.fs.FsContentProvider;
import org.argeo.cms.swt.CmsSwtUtils;
import org.eclipse.swt.SWT;
-import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
-import org.eclipse.swt.widgets.Tree;
-import org.eclipse.swt.widgets.TreeItem;
public class AcrContentTreeView extends Composite {
- private Tree tree;
+ private static final long serialVersionUID = -3707881216246077323L;
+ private SwtContentHierarchicalPart hPart;
+// private Tree tree;
private Table table;
private Content rootContent;
initTree();
GridData treeGd = CmsSwtUtils.fillHeight();
treeGd.widthHint = 300;
- tree.setLayoutData(treeGd);
+ hPart.getArea().setLayoutData(treeGd);
initTable();
table.setLayoutData(CmsSwtUtils.fillAll());
}
protected void initTree() {
- tree = new Tree(this, 0);
- for (Content c : rootContent) {
- TreeItem root = new TreeItem(tree, 0);
- root.setText(c.getName().toString());
- root.setData(c);
- new TreeItem(root, 0);
- }
- tree.addListener(SWT.Expand, event -> {
- final TreeItem root = (TreeItem) event.item;
- TreeItem[] items = root.getItems();
- for (TreeItem item : items) {
- if (item.getData() != null)
- return;
- item.dispose();
- }
- Content content = (Content) root.getData();
- for (Content c : content) {
- TreeItem item = new TreeItem(root, 0);
- item.setText(c.getName().toString());
- item.setData(c);
- boolean hasChildren = true;
- if (hasChildren) {
- new TreeItem(item, 0);
- }
- }
- });
- tree.addListener(SWT.Selection, event -> {
- TreeItem item = (TreeItem) event.item;
- selected = (Content) item.getData();
+ hPart = new SwtContentHierarchicalPart(this, getStyle());
+ hPart.setInput(rootContent);
+ hPart.onSelected((o) -> {
+ Content content = (Content) o;
+ selected = content;
refreshTable();
+
});
+// tree = new Tree(this, 0);
+// for (Content c : rootContent) {
+// TreeItem root = new TreeItem(tree, 0);
+// root.setText(c.getName().toString());
+// root.setData(c);
+// new TreeItem(root, 0);
+// }
+// tree.addListener(SWT.Expand, event -> {
+// final TreeItem root = (TreeItem) event.item;
+// TreeItem[] items = root.getItems();
+// for (TreeItem item : items) {
+// if (item.getData() != null)
+// return;
+// item.dispose();
+// }
+// Content content = (Content) root.getData();
+// for (Content c : content) {
+// TreeItem item = new TreeItem(root, 0);
+// item.setText(c.getName().toString());
+// item.setData(c);
+// boolean hasChildren = true;
+// if (hasChildren) {
+// new TreeItem(item, 0);
+// }
+// }
+// });
+// tree.addListener(SWT.Selection, event -> {
+// TreeItem item = (TreeItem) event.item;
+// selected = (Content) item.getData();
+// refreshTable();
+// });
}
protected void initTable() {
table.getColumn(1).pack();
}
- public static void main(String[] args) {
- Path basePath;
- if (args.length > 0) {
- basePath = Paths.get(args[0]);
- } else {
- basePath = Paths.get(System.getProperty("user.home"));
- }
-
- final Display display = new Display();
- final Shell shell = new Shell(display);
- shell.setText(basePath.toString());
- shell.setLayout(new FillLayout());
-
- FsContentProvider contentSession = new FsContentProvider("/", basePath);
-// GcrContentTreeView treeView = new GcrContentTreeView(shell, 0, contentSession.get("/"));
-
- shell.setSize(shell.computeSize(800, 600));
- shell.open();
- while (!shell.isDisposed()) {
- if (!display.readAndDispatch())
- display.sleep();
- }
- display.dispose();
- }
+// public static void main(String[] args) {
+// Path basePath;
+// if (args.length > 0) {
+// basePath = Paths.get(args[0]);
+// } else {
+// basePath = Paths.get(System.getProperty("user.home"));
+// }
+//
+// final Display display = new Display();
+// final Shell shell = new Shell(display);
+// shell.setText(basePath.toString());
+// shell.setLayout(new FillLayout());
+//
+// FsContentProvider contentSession = new FsContentProvider("/", basePath);
+//// GcrContentTreeView treeView = new GcrContentTreeView(shell, 0, contentSession.get("/"));
+//
+// shell.setSize(shell.computeSize(800, 600));
+// shell.open();
+// while (!shell.isDisposed()) {
+// if (!display.readAndDispatch())
+// display.sleep();
+// }
+// display.dispose();
+// }
}
--- /dev/null
+package org.argeo.cms.swt.acr;
+
+import java.util.Iterator;
+
+import org.argeo.api.acr.Content;
+import org.argeo.cms.swt.widgets.SwtHierarchicalPart;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.TreeItem;
+
+public class SwtContentHierarchicalPart extends SwtHierarchicalPart {
+
+ public SwtContentHierarchicalPart(Composite parent, int style) {
+ super(parent, style);
+ }
+
+ public Content getContent() {
+ return (Content) getInput();
+ }
+
+ @Override
+ protected void refreshRootItem(TreeItem item) {
+ refreshItem(null, item);
+ }
+
+ @Override
+ protected void refreshItem(TreeItem parentItem, TreeItem item) {
+ int index = getTree().indexOf(item);
+ Content parentContent = parentItem == null ? getContent() : (Content) parentItem.getData();
+ Content content = null;
+ int count = 0;
+ children: for (Content c : parentContent) {
+ if (count == index) {
+ content = c;
+ break children;
+ }
+ count++;
+ }
+ item.setData(content);
+ item.setText(content.getName().toString());
+ item.setItemCount(getChildrenCount(content));
+ }
+
+ @Override
+ protected int getRootItemCount() {
+ return getChildrenCount(getContent());
+ }
+
+ static int getChildrenCount(Content content) {
+ int count = 0;
+ for (Iterator<Content> it = content.iterator(); it.hasNext();it.next()) {
+ count++;
+ }
+ return count;
+ }
+}
this.onAction = onAction;
}
+ public Composite getArea() {
+ return area;
+ }
+
}
import java.util.List;
import java.util.Optional;
import java.util.Set;
+import java.util.TreeSet;
import java.util.concurrent.ForkJoinPool;
import javax.jcr.Node;
// private Node jcrNode;
private JcrContentProvider provider;
- private ProvidedSession session;
private String jcrWorkspace;
private String jcrPath;
protected JcrContent(ProvidedSession session, JcrContentProvider provider, String jcrWorkspace, String jcrPath) {
- this.session = session;
+ super(session);
this.provider = provider;
this.jcrWorkspace = jcrWorkspace;
this.jcrPath = jcrPath;
public Node getJcrNode() {
try {
// TODO caching?
- return provider.getJcrSession(session, jcrWorkspace).getNode(jcrPath);
+ return provider.getJcrSession(getSession(), jcrWorkspace).getNode(jcrPath);
} catch (RepositoryException e) {
throw new JcrException("Cannot retrieve " + jcrPath + " from workspace " + jcrWorkspace, e);
}
@Override
public Content next() {
- current = new JcrContent(session, provider, jcrWorkspace, Jcr.getPath(nodeIterator.nextNode()));
+ current = new JcrContent(getSession(), provider, jcrWorkspace, Jcr.getPath(nodeIterator.nextNode()));
return current;
}
public Content getParent() {
if (Jcr.isRoot(getJcrNode())) // root
return null;
- return new JcrContent(session, provider, jcrWorkspace, Jcr.getParentPath(getJcrNode()));
+ return new JcrContent(getSession(), provider, jcrWorkspace, Jcr.getParentPath(getJcrNode()));
}
@Override
boolean exists() {
try {
- return provider.getJcrSession(session, jcrWorkspace).itemExists(jcrPath);
+ return provider.getJcrSession(getSession(), jcrWorkspace).itemExists(jcrPath);
} catch (RepositoryException e) {
throw new JcrException("Cannot check whether " + jcrPath + " exists", e);
}
ForkJoinPool.commonPool().execute(() -> {
try (PipedOutputStream out = new PipedOutputStream(in)) {
- provider.getJcrSession(session, jcrWorkspace).exportDocumentView(jcrPath, out, true, false);
+ provider.getJcrSession(getSession(), jcrWorkspace).exportDocumentView(jcrPath, out, true, false);
out.flush();
} catch (IOException | RepositoryException e) {
throw new RuntimeException("Cannot export " + jcrPath + " in workspace " + jcrWorkspace, e);
return super.open(clss);
}
- @Override
- public ProvidedSession getSession() {
- return session;
- }
-
@Override
public ContentProvider getProvider() {
return provider;
}
}
+ /*
+ * TYPING
+ */
+ @Override
+ public List<QName> getTypes() {
+ try {
+// Node node = getJcrNode();
+// List<QName> res = new ArrayList<>();
+// res.add(nodeTypeToQName(node.getPrimaryNodeType()));
+// for (NodeType mixin : node.getMixinNodeTypes()) {
+// res.add(nodeTypeToQName(mixin));
+// }
+// return res;
+ Node context = getJcrNode();
+
+ List<QName> res = new ArrayList<>();
+ // primary node type
+ NodeType primaryType = context.getPrimaryNodeType();
+ res.add(nodeTypeToQName(primaryType));
+
+ Set<QName> secondaryTypes = new TreeSet<>();
+ for (NodeType mixinType : context.getMixinNodeTypes()) {
+ secondaryTypes.add(nodeTypeToQName(mixinType));
+ }
+ for (NodeType superType : primaryType.getDeclaredSupertypes()) {
+ secondaryTypes.add(nodeTypeToQName(superType));
+ }
+ // mixins
+ for (NodeType mixinType : context.getMixinNodeTypes()) {
+ for (NodeType superType : mixinType.getDeclaredSupertypes()) {
+ secondaryTypes.add(nodeTypeToQName(superType));
+ }
+ }
+// // entity type
+// if (context.isNodeType(EntityType.entity.get())) {
+// if (context.hasProperty(EntityNames.ENTITY_TYPE)) {
+// String entityTypeName = context.getProperty(EntityNames.ENTITY_TYPE).getString();
+// if (byType.containsKey(entityTypeName)) {
+// types.add(entityTypeName);
+// }
+// }
+// }
+ res.addAll(secondaryTypes);
+ return res;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot list node types from " + getJcrNode(), e);
+ }
+ }
+
+ private QName nodeTypeToQName(NodeType nodeType) {
+ String name = nodeType.getName();
+ return QName.valueOf(name);
+ }
+
/*
* STATIC UTLITIES
*/
void remove();
+ /*
+ * TYPING
+ */
+ List<QName> getTypes();
+
/*
* DEFAULT METHODS
*/
- default <A> A adapt(Class<A> clss) throws IllegalArgumentException {
+ default <A> A adapt(Class<A> clss) {
throw new UnsupportedOperationException("Cannot adapt content " + this + " to " + clss.getName());
}
// public final static String CR_POSIX_NAMESPACE_URI = CR_NAMESPACE_URI + "/posix";
public final static String CR_DEFAULT_PREFIX = "cr";
+
+ public final static String LDAP_NAMESPACE_URI = "http://argeo.org/ns/ldap";
+ public final static String LDAP_DEFAULT_PREFIX = "ldap";
+
+
private final ContentName value;
CrName() {
package org.argeo.api.acr;
import java.util.Collections;
+import java.util.Comparator;
import java.util.Iterator;
import java.util.Objects;
import java.util.Set;
return prefix + ":" + name.getLocalPart();
}
+ public final static Comparator<QName> QNAME_COMPARATOR = new Comparator<QName>() {
+
+ @Override
+ public int compare(QName qn1, QName qn2) {
+ if (Objects.equals(qn1.getNamespaceURI(), qn2.getNamespaceURI())) {// same namespace
+ return qn1.getLocalPart().compareTo(qn2.getLocalPart());
+ } else {
+ return qn1.getNamespaceURI().compareTo(qn2.getNamespaceURI());
+ }
+ }
+
+ };
+
/** singleton */
private NamespaceUtils() {
}
</service>
<reference bind="setCmsState" cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
<reference bind="setUuidFactory" cardinality="1..1" interface="org.argeo.api.uuid.UuidFactory" name="UuidFactory" policy="static"/>
+ <reference bind="setUserManager" cardinality="1..1" interface="org.argeo.cms.CmsUserManager" name="CmsUserManager" policy="static"/>
</scr:component>
void expireAuthToken(String token);
void expireAuthTokens(Subject subject);
+
+ UserDirectory getUserDirectory(User user);
// User createUserFromPerson(Node person);
import org.argeo.api.acr.Content;
import org.argeo.api.acr.CrName;
import org.argeo.api.acr.spi.ProvidedContent;
+import org.argeo.api.acr.spi.ProvidedSession;
/** Partial reference implementation of a {@link ProvidedContent}. */
public abstract class AbstractContent extends AbstractMap<QName, Object> implements ProvidedContent {
+ private final ProvidedSession session;
+
+ public AbstractContent(ProvidedSession session) {
+ this.session = session;
+ }
/*
* ATTRIBUTES OPERATIONS
*/
- protected abstract Iterable<QName> keys();
-
- protected abstract void removeAttr(QName key);
+// protected abstract Iterable<QName> keys();
+//
+// protected abstract void removeAttr(QName key);
@Override
public Set<Entry<QName, Object>> entrySet() {
collectAncestors(ancestors, this);
return ancestors.size();
}
-
+
@Override
public String getSessionLocalId() {
return getPath();
}
+ /*
+ * SESSION
+ */
+
+ @Override
+ public ProvidedSession getSession() {
+ return session;
+ }
+
+ /*
+ * TYPING
+ */
+
+ @Override
+ public List<QName> getTypes() {
+ return new ArrayList<>();
+ }
+
/*
* UTILITIES
*/
// return "content " + getPath();
// }
+ /*
+ * DEFAULTS
+ */
+ // - no children
+ // - no attributes
+ // - cannot be modified
+ @Override
+ public Iterator<Content> iterator() {
+ return Collections.emptyIterator();
+ }
+
+ @Override
+ public Content add(QName name, QName... classes) {
+ throw new UnsupportedOperationException("Content cannot be added.");
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("Content cannot be removed.");
+ }
+
+ protected Iterable<QName> keys() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public <A> Optional<A> get(QName key, Class<A> clss) {
+ return null;
+ }
+
+ protected void removeAttr(QName key) {
+ throw new UnsupportedOperationException("Attributes cannot be removed.");
+ }
+
/*
* SUB CLASSES
*/
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.HashSet;
import java.util.Set;
import javax.xml.namespace.QName;
private CmsContentSession systemSession;
+ private Set<ContentProvider> providersToAdd = new HashSet<>();
+
// utilities
/** Should be used only to copy source and results. */
private TransformerFactory identityTransformerFactory = TransformerFactory.newInstance();
*/
public void addProvider(ContentProvider provider) {
- mountManager.addStructuralContentProvider(provider);
+ if (mountManager == null)
+ providersToAdd.add(provider);
+ else
+ mountManager.addStructuralContentProvider(provider);
}
public void registerTypes(String prefix, String namespaceURI, String schemaSystemId) {
throw new IllegalStateException("Cannot init ACR root " + path, e);
}
+ // add content providers already notified
+ for (ContentProvider contentProvider : providersToAdd)
+ addProvider(contentProvider);
+ providersToAdd.clear();
}
public void writeDom(Document document, OutputStream out) throws IOException {
*/
public class CmsContentRepository extends AbstractContentRepository {
public final static String RUN_BASE = "/run";
+ public final static String DIRECTORY_BASE = "/directory";
private Map<CmsSession, CmsContentSession> userSessions = Collections.synchronizedMap(new HashMap<>());
package org.argeo.cms.acr;
import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
import java.util.function.BiConsumer;
import javax.xml.namespace.QName;
import org.argeo.api.acr.Content;
-import org.argeo.api.cms.CmsSession;
/** Utilities and routines around {@link Content}. */
public class ContentUtils {
// return t instanceof String;
// }
+ public static final char SLASH = '/';
+ public static final String ROOT_SLASH = "" + SLASH;
+
/**
* Split a path (with '/' separator) in an array of length 2, the first part
* being the parent path (which could be either absolute or relative), the
- * second one being the last segment, (guaranteed to be with '/').
+ * second one being the last segment, (guaranteed to be without a '/').
*/
public static String[] getParentPath(String path) {
- int parentIndex = path.lastIndexOf('/');
- // TODO make it more robust
- return new String[] { parentIndex != 0 ? path.substring(0, parentIndex) : "/",
+ if (path == null)
+ throw new IllegalArgumentException("Path cannot be null");
+ if (path.length() == 0)
+ throw new IllegalArgumentException("Path cannot be empty");
+ checkDoubleSlash(path);
+ int parentIndex = path.lastIndexOf(SLASH);
+ if (parentIndex == path.length() - 1) {// trailing '/'
+ path = path.substring(0, path.length() - 1);
+ parentIndex = path.lastIndexOf(SLASH);
+ }
+
+ if (parentIndex == -1) // no '/'
+ return new String[] { "", path };
+
+ return new String[] { parentIndex != 0 ? path.substring(0, parentIndex) : "" + SLASH,
path.substring(parentIndex + 1) };
}
+ public static List<String> toPathSegments(String path) {
+ List<String> res = new ArrayList<>();
+ if ("".equals(path) || ROOT_SLASH.equals(path))
+ return res;
+ collectPathSegments(path, res);
+ return res;
+ }
+
+ private static void collectPathSegments(String path, List<String> segments) {
+ String[] parent = getParentPath(path);
+ if ("".equals(parent[1])) // root
+ return;
+ segments.add(0, parent[1]);
+ if ("".equals(parent[0])) // end
+ return;
+ collectPathSegments(parent[0], segments);
+ }
+
+ public static void checkDoubleSlash(String path) {
+ if (path.contains(SLASH + "" + SLASH))
+ throw new IllegalArgumentException("Path " + path + " contains //");
+ }
+
/** Singleton. */
private ContentUtils() {
import org.apache.xerces.xs.XSTerm;
import org.apache.xerces.xs.XSTypeDefinition;
import org.argeo.api.acr.CrAttributeType;
+import org.argeo.api.acr.NamespaceUtils;
import org.argeo.api.cms.CmsLog;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
// types
- types = new TreeMap<>((qn1, qn2) -> {
- if (Objects.equals(qn1.getNamespaceURI(), qn2.getNamespaceURI())) {// same namespace
- return qn1.getLocalPart().compareTo(qn2.getLocalPart());
- } else {
- return qn1.getNamespaceURI().compareTo(qn2.getNamespaceURI());
- }
- });
+ types = new TreeMap<>(NamespaceUtils.QNAME_COMPARATOR);
}
--- /dev/null
+package org.argeo.cms.acr.directory;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentName;
+import org.argeo.api.acr.ContentNotFoundException;
+import org.argeo.api.acr.CrName;
+import org.argeo.api.acr.spi.ContentProvider;
+import org.argeo.api.acr.spi.ProvidedContent;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.cms.CmsUserManager;
+import org.argeo.cms.acr.AbstractContent;
+import org.argeo.cms.acr.ContentUtils;
+import org.argeo.osgi.useradmin.HierarchyUnit;
+import org.argeo.osgi.useradmin.LdapNameUtils;
+import org.argeo.osgi.useradmin.UserDirectory;
+import org.osgi.service.useradmin.User;
+
+public class DirectoryContentProvider implements ContentProvider {
+ private String mountPath;
+ private String mountName;
+
+ private CmsUserManager userManager;
+
+ public DirectoryContentProvider(String mountPath, CmsUserManager userManager) {
+ this.mountPath = mountPath;
+ List<String> mountSegments = ContentUtils.toPathSegments(mountPath);
+ this.mountName = mountSegments.get(mountSegments.size() - 1);
+ this.userManager = userManager;
+ }
+
+ @Override
+ public ProvidedContent get(ProvidedSession session, String relativePath) {
+ List<String> segments = ContentUtils.toPathSegments(relativePath);
+ if (segments.size() == 0)
+ return new UserManagerContent(session);
+ String userDirectoryDn = segments.get(0);
+ UserDirectory userDirectory = null;
+ userDirectories: for (UserDirectory ud : userManager.getUserDirectories()) {
+ if (userDirectoryDn.equals(ud.getBasePath())) {
+ userDirectory = ud;
+ break userDirectories;
+ }
+ }
+ if (userDirectory == null)
+ throw new ContentNotFoundException("Cannot find user directory " + userDirectoryDn);
+ if (segments.size() == 1) {
+ return new HierarchyUnitContent(session, this, userDirectory);
+ } else {
+ LdapName dn;
+ try {
+ dn = LdapNameUtils.toLdapName(userDirectoryDn);
+ for (int i = 1; i < segments.size(); i++) {
+ dn.add(segments.get(i));
+ }
+ } catch (InvalidNameException e) {
+ throw new IllegalStateException("Cannot interpret " + segments + " as DN", e);
+ }
+ User user = userManager.getUser(dn.toString());
+ if (user != null) {
+ HierarchyUnit parent = userDirectory.getHierarchyUnit(user);
+ return new RoleContent(session, this, new HierarchyUnitContent(session, this, parent), user);
+ }
+ HierarchyUnit hierarchyUnit = userDirectory.getHierarchyUnit(dn.toString());
+ if (hierarchyUnit == null)
+ throw new ContentNotFoundException("Cannot find " + dn);
+ return new HierarchyUnitContent(session, this, hierarchyUnit);
+ }
+ }
+
+ @Override
+ public boolean exists(ProvidedSession session, String relativePath) {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public String getMountPath() {
+ return mountPath;
+ }
+
+ @Override
+ public String getNamespaceURI(String prefix) {
+ if (CrName.LDAP_DEFAULT_PREFIX.equals(prefix))
+ return CrName.LDAP_NAMESPACE_URI;
+ throw new IllegalArgumentException("Only prefix " + CrName.LDAP_DEFAULT_PREFIX + " is supported");
+ }
+
+ @Override
+ public Iterator<String> getPrefixes(String namespaceURI) {
+ if (CrName.LDAP_NAMESPACE_URI.equals(namespaceURI))
+ return Collections.singletonList(CrName.LDAP_DEFAULT_PREFIX).iterator();
+ throw new IllegalArgumentException("Only namespace URI " + CrName.LDAP_NAMESPACE_URI + " is supported");
+ }
+
+ public void setUserManager(CmsUserManager userManager) {
+ this.userManager = userManager;
+ }
+
+ UserManagerContent getRootContent(ProvidedSession session) {
+ return new UserManagerContent(session);
+ }
+
+ class UserManagerContent extends AbstractContent {
+
+ public UserManagerContent(ProvidedSession session) {
+ super(session);
+ }
+
+ @Override
+ public ContentProvider getProvider() {
+ return DirectoryContentProvider.this;
+ }
+
+ @Override
+ public QName getName() {
+ return new ContentName(mountName);
+ }
+
+ @Override
+ public Content getParent() {
+ return null;
+ }
+
+ @Override
+ public Iterator<Content> iterator() {
+ List<Content> res = new ArrayList<>();
+ for (UserDirectory userDirectory : userManager.getUserDirectories()) {
+ HierarchyUnitContent content = new HierarchyUnitContent(getSession(), DirectoryContentProvider.this,
+ userDirectory);
+ res.add(content);
+ }
+ return res.iterator();
+ }
+
+ }
+}
--- /dev/null
+package org.argeo.cms.acr.directory;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentName;
+import org.argeo.api.acr.CrName;
+import org.argeo.api.acr.spi.ContentProvider;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.cms.acr.AbstractContent;
+import org.argeo.osgi.useradmin.HierarchyUnit;
+import org.osgi.service.useradmin.Role;
+
+public class HierarchyUnitContent extends AbstractContent {
+ private HierarchyUnit hierarchyUnit;
+
+ private DirectoryContentProvider provider;
+
+ public HierarchyUnitContent(ProvidedSession session, DirectoryContentProvider provider,
+ HierarchyUnit hierarchyUnit) {
+ super(session);
+ Objects.requireNonNull(hierarchyUnit);
+ this.provider = provider;
+ this.hierarchyUnit = hierarchyUnit;
+ }
+
+ @Override
+ public ContentProvider getProvider() {
+ return provider;
+ }
+
+ @Override
+ public QName getName() {
+ if (hierarchyUnit.getParent() == null) {// base DN
+ String baseDn = hierarchyUnit.getBasePath();
+ return new ContentName(baseDn);
+ }
+ String name = hierarchyUnit.getHierarchyUnitName();
+ return new ContentName(name);
+ }
+
+ @Override
+ public Content getParent() {
+ HierarchyUnit parentHu = hierarchyUnit.getParent();
+ if (parentHu == null) {
+ return provider.getRootContent(getSession());
+ }
+ return new HierarchyUnitContent(getSession(), provider, parentHu);
+ }
+
+ @Override
+ public Iterator<Content> iterator() {
+ List<Content> lst = new ArrayList<>();
+ for (int i = 0; i < hierarchyUnit.getHierarchyChildCount(); i++)
+ lst.add(new HierarchyUnitContent(getSession(), provider, hierarchyUnit.getHierarchyChild(i)));
+
+ for (Role role : hierarchyUnit.getRoles(null, false))
+ lst.add(new RoleContent(getSession(), provider, this, role));
+ return lst.iterator();
+ }
+
+ /*
+ * TYPING
+ */
+
+ @Override
+ public List<QName> getTypes() {
+ List<QName> res = new ArrayList<>();
+ res.add(CrName.COLLECTION.get());
+ return res;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.acr.directory;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.TreeSet;
+
+import javax.naming.ldap.LdapName;
+import javax.swing.GroupLayout.Group;
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.ContentName;
+import org.argeo.api.acr.CrName;
+import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.api.acr.spi.ContentProvider;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.cms.acr.AbstractContent;
+import org.argeo.osgi.useradmin.LdapNameUtils;
+import org.argeo.util.naming.LdapAttrs;
+import org.argeo.util.naming.LdapObjs;
+import org.osgi.service.useradmin.Role;
+import org.osgi.service.useradmin.User;
+
+public class RoleContent extends AbstractContent {
+
+ private DirectoryContentProvider provider;
+ private HierarchyUnitContent parent;
+ private Role role;
+
+ public RoleContent(ProvidedSession session, DirectoryContentProvider provider, HierarchyUnitContent parent,
+ Role role) {
+ super(session);
+ this.provider = provider;
+ this.parent = parent;
+ this.role = role;
+ }
+
+ @Override
+ public ContentProvider getProvider() {
+ return provider;
+ }
+
+ @Override
+ public QName getName() {
+ LdapName dn = LdapNameUtils.toLdapName(role.getName());
+ String name = LdapNameUtils.getLastRdnAsString(dn);
+ return new ContentName(name);
+ }
+
+ @Override
+ public Content getParent() {
+ return parent;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <A> Optional<A> get(QName key, Class<A> clss) {
+ String attrName = key.getLocalPart();
+ Object value = role.getProperties().get(attrName);
+ if (value == null)
+ return Optional.empty();
+ // TODO deal with type and multiple
+ return Optional.of((A) value);
+ }
+
+ @Override
+ protected Iterable<QName> keys() {
+ Set<QName> keys = new TreeSet<>(NamespaceUtils.QNAME_COMPARATOR);
+ keys: for (Enumeration<String> it = role.getProperties().keys(); it.hasMoreElements();) {
+ String key = it.nextElement();
+ if (key.equalsIgnoreCase(LdapAttrs.objectClass.name()))
+ continue keys;
+ ContentName name = new ContentName(CrName.LDAP_NAMESPACE_URI, key, provider);
+ keys.add(name);
+ }
+ return keys;
+ }
+
+ @Override
+ public List<QName> getTypes() {
+ List<QName> contentClasses = new ArrayList<>();
+ keys: for (Enumeration<String> it = role.getProperties().keys(); it.hasMoreElements();) {
+ String key = it.nextElement();
+ if (key.equalsIgnoreCase(LdapAttrs.objectClass.name())) {
+ String[] objectClasses = role.getProperties().get(key).toString().split("\\n");
+ objectClasses: for (String objectClass : objectClasses) {
+ if (LdapObjs.top.name().equalsIgnoreCase(objectClass))
+ continue objectClasses;
+ contentClasses.add(new ContentName(CrName.LDAP_NAMESPACE_URI, objectClass, provider));
+ }
+ break keys;
+ }
+ }
+ return contentClasses;
+ }
+
+ @Override
+ public Object put(QName key, Object value) {
+ Object previous = get(key);
+ // TODO deal with typing
+ role.getProperties().put(key.getLocalPart(), value);
+ return previous;
+ }
+
+ @Override
+ protected void removeAttr(QName key) {
+ role.getProperties().remove(key.getLocalPart());
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <A> A adapt(Class<A> clss) {
+ if (clss.equals(Group.class))
+ return (A) role;
+ else if (clss.equals(User.class))
+ return (A) role;
+ else if (clss.equals(Role.class))
+ return (A) role;
+ return super.adapt(clss);
+ }
+
+}
import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.UserDefinedFileAttributeView;
import java.time.Instant;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
POSIX_KEYS.put(CrName.PERMISSIONS.get(), "posix:permissions");
}
- private final ProvidedSession session;
private final FsContentProvider provider;
private final Path path;
private final boolean isRoot;
private final QName name;
protected FsContent(ProvidedSession session, FsContentProvider contentProvider, Path path) {
- this.session = session;
+ super(session);
this.provider = contentProvider;
this.path = path;
this.isRoot = contentProvider.isMountRoot(path);
try {
for (String name : udfav.list()) {
QName providerName = NamespaceUtils.parsePrefixedName(provider, name);
- QName sessionName = new ContentName(providerName, session);
+ QName sessionName = new ContentName(providerName, getSession());
result.add(sessionName);
}
} catch (IOException e) {
Optional<String> isMount = fsContent.get(CrName.MOUNT.get(), String.class);
if (isMount.orElse("false").equals("true")) {
QName[] classes = null;
- ContentProvider contentProvider = session.getRepository().getMountContentProvider(fsContent,
- false, classes);
- Content mountedContent = contentProvider.get(session, "");
+ ContentProvider contentProvider = getSession().getRepository()
+ .getMountContentProvider(fsContent, false, classes);
+ Content mountedContent = contentProvider.get(getSession(), "");
return mountedContent;
} else {
return (Content) fsContent;
throw new ContentResourceException("Cannot create new content", e);
}
- if (session.getRepository().shouldMount(classes)) {
- ContentProvider contentProvider = session.getRepository().getMountContentProvider(fsContent, true, classes);
- Content mountedContent = contentProvider.get(session, "");
+ if (getSession().getRepository().shouldMount(classes)) {
+ ContentProvider contentProvider = getSession().getRepository().getMountContentProvider(fsContent, true,
+ classes);
+ Content mountedContent = contentProvider.get(getSession(), "");
fsContent.put(CrName.MOUNT.get(), "true");
return mountedContent;
if (mountPath == null || mountPath.equals("/"))
return null;
String[] parent = ContentUtils.getParentPath(mountPath);
- return session.get(parent[0]);
+ return getSession().get(parent[0]);
}
return new FsContent(this, path.getParent());
}
}
/*
- * ACCESSORS
+ * TYPING
*/
+
@Override
- public ProvidedSession getSession() {
- return session;
+ public List<QName> getTypes() {
+ List<QName> res = new ArrayList<>();
+ if (Files.isDirectory(path))
+ res.add(CrName.COLLECTION.get());
+ // TODO add other types
+ return res;
}
+ /*
+ * ACCESSORS
+ */
+
@Override
public FsContentProvider getProvider() {
return provider;
package org.argeo.cms.acr.xml;
import java.nio.CharBuffer;
+import java.nio.file.Files;
+import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import org.argeo.api.acr.Content;
import org.argeo.api.acr.ContentName;
+import org.argeo.api.acr.CrName;
import org.argeo.api.acr.spi.ProvidedContent;
import org.argeo.api.acr.spi.ProvidedSession;
import org.argeo.cms.acr.AbstractContent;
/** Content persisted as a DOM element. */
public class DomContent extends AbstractContent implements ProvidedContent {
- private final ProvidedSession session;
private final DomContentProvider provider;
private final Element element;
private Boolean hasText = null;
public DomContent(ProvidedSession session, DomContentProvider contentProvider, Element element) {
- this.session = session;
+ super(session);
this.provider = contentProvider;
this.element = element;
}
if (element.getParentNode() == null) {// root
String mountPath = provider.getMountPath();
if (mountPath != null) {
- Content mountPoint = session.getMountPoint(mountPath);
+ Content mountPoint = getSession().getMountPoint(mountPath);
return mountPoint.getName();
}
}
@Override
public Iterator<Content> iterator() {
NodeList nodeList = element.getChildNodes();
- return new ElementIterator(this, session, provider, nodeList);
+ return new ElementIterator(this, getSession(), provider, nodeList);
}
@Override
if (mountPath == null)
return null;
String[] parent = ContentUtils.getParentPath(mountPath);
- return session.get(parent[0]);
+ return getSession().get(parent[0]);
}
if (parentNode instanceof Document)
return null;
if (String.class.isAssignableFrom(clss)) {
CompletableFuture<String> res = new CompletableFuture<>();
res.thenAccept((s) -> {
- session.notifyModification(this);
+ getSession().notifyModification(this);
element.setTextContent(s);
});
return (CompletableFuture<A>) res;
return super.write(clss);
}
+ /*
+ * TYPING
+ */
+ @Override
+ public List<QName> getTypes() {
+ List<QName> res = new ArrayList<>();
+ res.add(getName());
+ return res;
+ }
+
/*
* MOUNT MANAGEMENT
*/
return new DomContent(this, childElement);
}
- public ProvidedSession getSession() {
- return session;
- }
-
+ @Override
public DomContentProvider getProvider() {
return provider;
}
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.NavigableMap;
import java.util.Set;
+import java.util.TreeMap;
import java.util.TreeSet;
import java.util.UUID;
}
}
+ @Override
+ public UserDirectory getUserDirectory(User user) {
+ String name = user.getName();
+ NavigableMap<String, UserDirectory> possible = new TreeMap<>();
+ for (UserDirectory userDirectory : userDirectories.keySet()) {
+ if (name.endsWith(userDirectory.getBasePath())) {
+ possible.put(userDirectory.getBasePath(), userDirectory);
+ }
+ }
+ if (possible.size() == 0)
+ throw new IllegalStateException("No user directory found for user " + name);
+ return possible.lastEntry().getValue();
+ }
+
// public User createUserFromPerson(Node person) {
// String email = JcrUtils.get(person, LdapAttrs.mail.property());
// String dn = buildDefaultDN(email, Role.USER);
import java.util.Map;
import org.argeo.api.acr.spi.ContentProvider;
+import org.argeo.cms.CmsUserManager;
import org.argeo.cms.acr.CmsContentRepository;
+import org.argeo.cms.acr.directory.DirectoryContentProvider;
import org.argeo.cms.acr.fs.FsContentProvider;
-import org.argeo.util.OS;
public class DeployedContentRepository extends CmsContentRepository {
private final static String ROOT_XML = "cr:root.xml";
+ private CmsUserManager userManager;
+
@Override
public void start() {
try {
// FsContentProvider srvContentProvider = new FsContentProvider("/" + CmsConstants.SRV_WORKSPACE, srvPath, false);
// addProvider(srvContentProvider);
- Path runDirPath = KernelUtils.getOsgiInstancePath(CmsContentRepository.RUN_BASE);
+ // run dir
+ Path runDirPath = KernelUtils.getOsgiInstancePath(CmsContentRepository.RUN_BASE);
Files.createDirectories(runDirPath);
FsContentProvider runContentProvider = new FsContentProvider(CmsContentRepository.RUN_BASE, runDirPath);
addProvider(runContentProvider);
+
+ // users
+ DirectoryContentProvider directoryContentProvider = new DirectoryContentProvider(
+ CmsContentRepository.DIRECTORY_BASE, userManager);
+ addProvider(directoryContentProvider);
} catch (IOException e) {
throw new IllegalStateException("Cannot start content repository", e);
}
public void removeContentProvider(ContentProvider provider, Map<String, Object> properties) {
}
+ public void setUserManager(CmsUserManager userManager) {
+ this.userManager = userManager;
+ }
+
}
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Dictionary;
+import java.util.HashMap;
import java.util.concurrent.CompletableFuture;
import org.argeo.api.acr.ContentRepository;
import org.argeo.api.cms.CmsDeployment;
import org.argeo.api.cms.CmsState;
import org.argeo.api.uuid.UuidFactory;
+import org.argeo.cms.CmsUserManager;
import org.argeo.cms.acr.CmsUuidFactory;
+import org.argeo.cms.internal.auth.CmsUserManagerImpl;
import org.argeo.cms.internal.osgi.DeployConfig;
import org.argeo.cms.internal.runtime.CmsContextImpl;
import org.argeo.cms.internal.runtime.CmsDeploymentImpl;
import org.argeo.osgi.transaction.SimpleTransactionManager;
import org.argeo.osgi.transaction.WorkControl;
import org.argeo.osgi.transaction.WorkTransaction;
+import org.argeo.osgi.useradmin.UserDirectory;
import org.argeo.util.register.Component;
import org.argeo.util.register.SimpleRegister;
import org.osgi.service.useradmin.UserAdmin;
// User Admin
CmsUserAdmin userAdmin = new CmsUserAdmin();
-
Component<CmsUserAdmin> userAdminC = new Component.Builder<>(userAdmin) //
.addType(UserAdmin.class) //
.addDependency(transactionManagerC.getType(WorkControl.class), userAdmin::setTransactionManager, null) //
}, null) //
.build(register);
+ // User manager
+ CmsUserManagerImpl userManager = new CmsUserManagerImpl();
+ for (UserDirectory userDirectory : userAdmin.getUserDirectories()) {
+ // FIXME deal with properties
+ userManager.addUserDirectory(userDirectory, new HashMap<>());
+ }
+ Component<CmsUserManagerImpl> userManagerC = new Component.Builder<>(userManager) //
+ .addType(CmsUserManager.class) //
+ .addDependency(userAdminC.getType(UserAdmin.class), userManager::setUserAdmin, null) //
+ .addDependency(transactionManagerC.getType(WorkTransaction.class), userManager::setUserTransaction,
+ null) //
+ .build(register);
+
// Content Repository
DeployedContentRepository contentRepository = new DeployedContentRepository();
Component<DeployedContentRepository> contentRepositoryC = new Component.Builder<>(contentRepository) //
.addDeactivation(contentRepository::stop) //
.addDependency(cmsStateC.getType(CmsState.class), contentRepository::setCmsState, null) //
.addDependency(uuidFactoryC.getType(UuidFactory.class), contentRepository::setUuidFactory, null) //
+ .addDependency(userManagerC.getType(CmsUserManager.class), contentRepository::setUserManager, null) //
.build(register);
// CMS Context
throw new IllegalArgumentException("No child hierarchy unit available");
}
+ @Override
+ public HierarchyUnit getParent() {
+ return null;
+ }
+
@Override
public int getHierarchyUnitType() {
return 0;
@Override
public String getHierarchyUnitName() {
- String name = baseDn.getRdn(baseDn.size() - 1).getValue().toString();
+ String name = LdapNameUtils.getLastRdnAsString(baseDn);
// TODO check ou, o, etc.
return name;
}
+ @Override
+ public HierarchyUnit getHierarchyUnit(String path) {
+ return null;
+ }
+
+ @Override
+ public HierarchyUnit getHierarchyUnit(Role role) {
+ return null;
+ }
+
@Override
public List<? extends Role> getRoles(String filter, boolean deep) {
try {
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.TreeSet;
import javax.naming.InvalidNameException;
import javax.naming.ldap.LdapName;
protected void preDestroy(UserDirectory userDirectory) {
}
+ public Set<UserDirectory> getUserDirectories() {
+ TreeSet<UserDirectory> res = new TreeSet<>((o1, o2) -> o1.getBasePath().compareTo(o2.getBasePath()));
+ res.addAll(businessRoles.values());
+ return res;
+ }
}
package org.argeo.osgi.useradmin;
import java.util.List;
+import java.util.Map;
import org.osgi.service.useradmin.Role;
int getHierarchyChildCount();
+ HierarchyUnit getParent();
+
HierarchyUnit getHierarchyChild(int i);
int getHierarchyUnitType();
String getBasePath();
List<? extends Role> getRoles(String filter, boolean deep);
+
+// Map<String,Object> getHierarchyProperties();
}
--- /dev/null
+package org.argeo.osgi.useradmin;
+
+import java.util.StringJoiner;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+
+/** Utilities to simplify using {@link LdapName}. */
+public class LdapNameUtils {
+
+ public static String toRevertPath(String dn, String prefix) {
+ if (!dn.endsWith(prefix))
+ throw new IllegalArgumentException("Prefix " + prefix + " not consistent with " + dn);
+ String relativeName = dn.substring(0, dn.length() - prefix.length() - 1);
+ LdapName name = toLdapName(relativeName);
+ StringJoiner path = new StringJoiner("/");
+ for (int i = 0; i < name.size(); i++) {
+ path.add(name.get(i));
+ }
+ return path.toString();
+ }
+
+ public static LdapName getParent(LdapName dn) {
+ try {
+ LdapName parent = (LdapName) dn.clone();
+ parent.remove(parent.size() - 1);
+ return parent;
+ } catch (InvalidNameException e) {
+ throw new IllegalArgumentException("Cannot get parent of " + dn, e);
+ }
+ }
+
+ public static LdapName toLdapName(String distinguishedName) {
+ try {
+ return new LdapName(distinguishedName);
+ } catch (InvalidNameException e) {
+ throw new IllegalArgumentException("Cannot parse " + distinguishedName + " as LDAP name", e);
+ }
+ }
+
+ public static Rdn getLastRdn(LdapName dn) {
+ return dn.getRdn(dn.size() - 1);
+ }
+
+ public static String getLastRdnAsString(LdapName dn) {
+ return getLastRdn(dn).toString();
+ }
+
+ public static String getLastRdnValue(LdapName dn) {
+ return getLastRdn(dn).getValue().toString();
+ }
+
+ /** singleton */
+ private LdapNameUtils() {
+
+ }
+}
// prepare not impacting
}
+// @Override
+// public HierarchyUnit getHierarchyUnit(String path) {
+// LdapName dn = LdapNameUtils.toLdapName(path);
+// Attributes attrs = ldapConnection.getAttributes(dn);
+//
+// }
+
}
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import javax.naming.directory.Attributes;
return children.size();
}
+ @Override
+ public HierarchyUnit getParent() {
+ return parent;
+ }
+
@Override
public HierarchyUnit getHierarchyChild(int i) {
return children.get(i);
@Override
public String getHierarchyUnitName() {
- String name = dn.getRdn(dn.size() - 1).getValue().toString();
+ String name = LdapNameUtils.getLastRdnAsString(dn);
// TODO check ou, o, etc.
return name;
}
import java.util.Iterator;
import java.util.List;
import java.util.Set;
+import java.util.StringJoiner;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
byte[] sha1hash(char[] password) {
byte[] hashedPassword = ("{SHA}"
+ Base64.getEncoder().encodeToString(DigestUtils.sha1(DigestUtils.charsToBytes(password))))
- .getBytes(StandardCharsets.UTF_8);
+ .getBytes(StandardCharsets.UTF_8);
return hashedPassword;
}
}
if (attr.size() == 1)
return value;
- if (!attr.getID().equals(LdapAttrs.objectClass.name()))
- return value;
+// if (!attr.getID().equals(LdapAttrs.objectClass.name()))
+// return value;
// special case for object class
NamingEnumeration<?> en = attr.getAll();
- Set<String> objectClasses = new HashSet<String>();
+ StringJoiner values = new StringJoiner("\n");
+ // Set<String> values = new HashSet<String>();
while (en.hasMore()) {
- String objectClass = en.next().toString();
- objectClasses.add(objectClass);
+ String v = en.next().toString();
+ values.add(v);
}
-
- if (objectClasses.contains(userAdmin.getUserObjectClass()))
- return userAdmin.getUserObjectClass();
- else if (objectClasses.contains(userAdmin.getGroupObjectClass()))
- return userAdmin.getGroupObjectClass();
- else
- return value;
+ return values.toString();
+// if (objectClasses.contains(userAdmin.getUserObjectClass()))
+// return userAdmin.getUserObjectClass();
+// else if (objectClasses.contains(userAdmin.getGroupObjectClass()))
+// return userAdmin.getGroupObjectClass();
+// else
+// return value;
} catch (NamingException e) {
- throw new UserDirectoryException("Cannot get value for attribute " + key, e);
+ throw new IllegalStateException("Cannot get value for attribute " + key, e);
}
}
init();
}
+ /*
+ * HIERARCHY
+ */
@Override
public int getHierarchyChildCount() {
return rootHierarchyUnits.size();
return rootHierarchyUnits.get(i);
}
- /*
- * HIERARCHY
- */
+ @Override
+ public HierarchyUnit getHierarchyUnit(String path) {
+ LdapName dn = LdapNameUtils.toLdapName(path);
+ return hierarchy.get(dn);
+ }
+
+ @Override
+ public HierarchyUnit getHierarchyUnit(Role role) {
+ LdapName dn = LdapNameUtils.toLdapName(role.getName());
+ // 2 levels
+ LdapName huDn = LdapNameUtils.getParent(LdapNameUtils.getParent(dn));
+ HierarchyUnit hierarchyUnit = hierarchy.get(huDn);
+ if (hierarchyUnit == null)
+ throw new IllegalStateException("No hierarchy unit found for " + role);
+ return hierarchyUnit;
+ }
}
import java.util.Optional;
import org.argeo.osgi.transaction.WorkControl;
+import org.osgi.service.useradmin.Role;
/** Information about a user directory. */
public interface UserDirectory extends HierarchyUnit {
String getGroupBase();
Optional<String> getRealm();
-
+
+ HierarchyUnit getHierarchyUnit(String path);
+
+ HierarchyUnit getHierarchyUnit(Role role);
+
@Deprecated
void setTransactionControl(WorkControl transactionControl);
}