import org.argeo.api.acr.Content;
import org.argeo.api.acr.CrAttributeType;
import org.argeo.api.acr.NamespaceUtils;
-import org.argeo.api.acr.spi.ContentProvider;
import org.argeo.api.acr.spi.ProvidedSession;
import org.argeo.api.cms.CmsConstants;
import org.argeo.cms.acr.AbstractContent;
/** A JCR {@link Node} accessed as {@link Content}. */
public class JcrContent extends AbstractContent {
-// private Node jcrNode;
-
private JcrContentProvider provider;
private String jcrWorkspace;
}
/*
- * READ/WRITE
+ * READ
*/
@Override
@Override
public <A> Optional<A> get(QName key, Class<A> clss) {
Object value = get(getJcrNode(), key.toString());
-// if (isDefaultAttrTypeRequested(clss)) {
-// return Optional.ofNullable((A) value);
-// }
return CrAttributeType.cast(clss, value);
}
}
}
- static Object convertSingleValue(Value value) throws JcrException, IllegalArgumentException {
- try {
- switch (value.getType()) {
- case PropertyType.STRING:
- return value.getString();
- case PropertyType.DOUBLE:
- return (Double) value.getDouble();
- case PropertyType.LONG:
- return (Long) value.getLong();
- case PropertyType.BOOLEAN:
- return (Boolean) value.getBoolean();
- case PropertyType.DATE:
- Calendar calendar = value.getDate();
- return calendar.toInstant();
- case PropertyType.BINARY:
- throw new IllegalArgumentException("Binary is not supported as an attribute");
- default:
- return value.getString();
- }
- } catch (RepositoryException e) {
- throw new JcrException("Cannot convert " + value + " to an object.", e);
- }
- }
-
- static Value convertSingleObject(ValueFactory factory, Object value) {
- if (value instanceof String string) {
- return factory.createValue(string);
- } else if (value instanceof Double dbl) {
- return factory.createValue(dbl);
- } else if (value instanceof Float flt) {
- return factory.createValue(flt);
- } else if (value instanceof Long lng) {
- return factory.createValue(lng);
- } else if (value instanceof Integer intg) {
- return factory.createValue(intg);
- } else if (value instanceof Boolean bool) {
- return factory.createValue(bool);
- } else if (value instanceof Instant instant) {
- GregorianCalendar calendar = new GregorianCalendar();
- calendar.setTime(Date.from(instant));
- return factory.createValue(calendar);
- } else {
- // TODO or use String by default?
- throw new IllegalArgumentException("Unsupported value " + value.getClass());
- }
- }
-
- @Override
- public Class<?> getType(QName key) {
- Node node = getJcrNode();
- String p = NamespaceUtils.toFullyQualified(key);
- try {
- if (node.hasProperty(p)) {
- Property property = node.getProperty(p);
- return switch (property.getType()) {
- case PropertyType.STRING:
- case PropertyType.NAME:
- case PropertyType.PATH:
- case PropertyType.DECIMAL:
- yield String.class;
- case PropertyType.LONG:
- yield Long.class;
- case PropertyType.DOUBLE:
- yield Double.class;
- case PropertyType.BOOLEAN:
- yield Boolean.class;
- case PropertyType.DATE:
- yield Instant.class;
- case PropertyType.WEAKREFERENCE:
- case PropertyType.REFERENCE:
- yield UUID.class;
- default:
- yield Object.class;
- };
- } else {
- // TODO does it make sense?
- return Object.class;
- }
- } catch (RepositoryException e) {
- throw new JcrException("Cannot get type of property " + p + " of " + jcrPath + " in " + jcrWorkspace, e);
- }
- }
-
@Override
public boolean isMultiple(QName key) {
Node node = getJcrNode();
}
}
- class JcrContentIterator implements Iterator<Content> {
- private final NodeIterator nodeIterator;
- // we keep track in order to be able to delete it
- private JcrContent current = null;
-
- protected JcrContentIterator(NodeIterator nodeIterator) {
- this.nodeIterator = nodeIterator;
- }
-
- @Override
- public boolean hasNext() {
- return nodeIterator.hasNext();
- }
-
- @Override
- public Content next() {
- current = new JcrContent(getSession(), provider, jcrWorkspace, Jcr.getPath(nodeIterator.nextNode()));
- return current;
- }
-
- @Override
- public void remove() {
- if (current != null) {
- Jcr.remove(current.getJcrNode());
- }
- }
-
- }
-
@Override
public String getPath() {
try {
return new JcrContent(getSession(), provider, jcrWorkspace, Jcr.getParentPath(getJcrNode()));
}
+ @Override
+ public int getSiblingIndex() {
+ return Jcr.getIndex(getJcrNode());
+ }
+
+ /*
+ * WRITE
+ */
+
+ protected Node openForEdit() {
+ Node node = getProvider().openForEdit(getSession(), jcrWorkspace, jcrPath);
+ getSession().notifyModification(this);
+ return node;
+ }
+
@Override
public Content add(QName name, QName... classes) {
if (classes.length > 0) {
QName primaryType = classes[0];
- Node child = Jcr.addNode(getJcrNode(), name.toString(), primaryType.toString());
+ Node node = openForEdit();
+ Node child = Jcr.addNode(node, name.toString(), primaryType.toString());
for (int i = 1; i < classes.length; i++) {
try {
child.addMixin(classes[i].toString());
@Override
public void remove() {
- Jcr.remove(getJcrNode());
+ Node node = openForEdit();
+ Jcr.remove(node);
}
@Override
protected void removeAttr(QName key) {
- Property property = Jcr.getProperty(getJcrNode(), key.toString());
+ Node node = openForEdit();
+ Property property = Jcr.getProperty(node, key.toString());
if (property != null) {
try {
property.remove();
public Object put(QName key, Object value) {
try {
String property = NamespaceUtils.toFullyQualified(key);
- Node node = getJcrNode();
+ Node node = openForEdit();
Object old = null;
if (node.hasProperty(property)) {
old = convertSingleValue(node.getProperty(property).getValue());
}
- Value newValue = convertSingleObject(getJcrSession().getValueFactory(), value);
+ Value newValue = convertSingleObject(node.getSession().getValueFactory(), value);
node.setProperty(property, newValue);
// FIXME proper edition
node.getSession().save();
}
}
+ @Override
+ public void addContentClasses(QName... contentClass) throws IllegalArgumentException, JcrException {
+ try {
+ Node node = openForEdit();
+ NodeTypeManager ntm = node.getSession().getWorkspace().getNodeTypeManager();
+ List<NodeType> nodeTypes = new ArrayList<>();
+ for (QName clss : contentClass) {
+ NodeType nodeType = ntm.getNodeType(NamespaceUtils.toFullyQualified(clss));
+ if (!nodeType.isMixin())
+ throw new IllegalArgumentException(clss + " is not a mixin");
+ nodeTypes.add(nodeType);
+ }
+ for (NodeType nodeType : nodeTypes) {
+ node.addMixin(nodeType.getName());
+ }
+ // FIXME proper edition
+ node.getSession().save();
+ } catch (RepositoryException e) {
+ throw new JcrException(
+ "Cannot add content classes " + contentClass + " to " + jcrPath + " in " + jcrWorkspace, e);
+ }
+ }
+
/*
* ACCESS
*/
}
@Override
- public ContentProvider getProvider() {
+ public JcrContentProvider getProvider() {
return provider;
}
/*
* TYPING
*/
+
+ static Object convertSingleValue(Value value) throws JcrException, IllegalArgumentException {
+ try {
+ switch (value.getType()) {
+ case PropertyType.STRING:
+ return value.getString();
+ case PropertyType.DOUBLE:
+ return (Double) value.getDouble();
+ case PropertyType.LONG:
+ return (Long) value.getLong();
+ case PropertyType.BOOLEAN:
+ return (Boolean) value.getBoolean();
+ case PropertyType.DATE:
+ Calendar calendar = value.getDate();
+ return calendar.toInstant();
+ case PropertyType.BINARY:
+ throw new IllegalArgumentException("Binary is not supported as an attribute");
+ default:
+ return value.getString();
+ }
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot convert " + value + " to an object.", e);
+ }
+ }
+
+ static Value convertSingleObject(ValueFactory factory, Object value) {
+ if (value instanceof String string) {
+ return factory.createValue(string);
+ } else if (value instanceof Double dbl) {
+ return factory.createValue(dbl);
+ } else if (value instanceof Float flt) {
+ return factory.createValue(flt);
+ } else if (value instanceof Long lng) {
+ return factory.createValue(lng);
+ } else if (value instanceof Integer intg) {
+ return factory.createValue(intg);
+ } else if (value instanceof Boolean bool) {
+ return factory.createValue(bool);
+ } else if (value instanceof Instant instant) {
+ GregorianCalendar calendar = new GregorianCalendar();
+ calendar.setTime(Date.from(instant));
+ return factory.createValue(calendar);
+ } else {
+ // TODO or use String by default?
+ throw new IllegalArgumentException("Unsupported value " + value.getClass());
+ }
+ }
+
+ @Override
+ public Class<?> getType(QName key) {
+ Node node = getJcrNode();
+ String p = NamespaceUtils.toFullyQualified(key);
+ try {
+ if (node.hasProperty(p)) {
+ Property property = node.getProperty(p);
+ return switch (property.getType()) {
+ case PropertyType.STRING:
+ case PropertyType.NAME:
+ case PropertyType.PATH:
+ case PropertyType.DECIMAL:
+ yield String.class;
+ case PropertyType.LONG:
+ yield Long.class;
+ case PropertyType.DOUBLE:
+ yield Double.class;
+ case PropertyType.BOOLEAN:
+ yield Boolean.class;
+ case PropertyType.DATE:
+ yield Instant.class;
+ case PropertyType.WEAKREFERENCE:
+ case PropertyType.REFERENCE:
+ yield UUID.class;
+ default:
+ yield Object.class;
+ };
+ } else {
+ // TODO does it make sense?
+ return Object.class;
+ }
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get type of property " + p + " of " + jcrPath + " in " + jcrWorkspace, e);
+ }
+ }
+
@Override
public List<QName> getContentClasses() {
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<>();
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) {
// return QName.valueOf(name);
}
- @Override
- public void addContentClasses(QName... contentClass) throws IllegalArgumentException, JcrException {
- try {
- NodeTypeManager ntm = getJcrSession().getWorkspace().getNodeTypeManager();
- List<NodeType> nodeTypes = new ArrayList<>();
- for (QName clss : contentClass) {
- NodeType nodeType = ntm.getNodeType(NamespaceUtils.toFullyQualified(clss));
- if (!nodeType.isMixin())
- throw new IllegalArgumentException(clss + " is not a mixin");
- nodeTypes.add(nodeType);
- }
- Node node = getJcrNode();
- for (NodeType nodeType : nodeTypes) {
- node.addMixin(nodeType.getName());
- }
- // FIXME proper edition
- node.getSession().save();
- } catch (RepositoryException e) {
- throw new JcrException(
- "Cannot add content classes " + contentClass + " to " + jcrPath + " in " + jcrWorkspace, e);
- }
- }
-
- @Override
- public int getSiblingIndex() {
- return Jcr.getIndex(getJcrNode());
- }
-
/*
* COMMON UTILITIES
*/
}
}
+ /*
+ * CONTENT ITERATOR
+ */
+
+ class JcrContentIterator implements Iterator<Content> {
+ private final NodeIterator nodeIterator;
+ // we keep track in order to be able to delete it
+ private JcrContent current = null;
+
+ protected JcrContentIterator(NodeIterator nodeIterator) {
+ this.nodeIterator = nodeIterator;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return nodeIterator.hasNext();
+ }
+
+ @Override
+ public Content next() {
+ current = new JcrContent(getSession(), provider, jcrWorkspace, Jcr.getPath(nodeIterator.nextNode()));
+ return current;
+ }
+
+ @Override
+ public void remove() {
+ if (current != null) {
+ Jcr.remove(current.getJcrNode());
+ }
+ }
+
+ }
+
}
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import javax.jcr.Node;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.version.VersionManager;
import javax.security.auth.Subject;
import org.apache.jackrabbit.core.SessionImpl;
import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.jcr.Jcr;
import org.argeo.jcr.JcrException;
import org.argeo.jcr.JcrUtils;
private Thread lastRetrievingThread = null;
+// private Thread writeThread;
+ private Map<String, Session> writeSessions = new HashMap<>();
+ /**
+ * Path of versionable nodes which have been modified during an edition cycle.
+ */
+ private Map<String, Set<String>> checkedInModified = new HashMap<>();
+ private Map<String, Set<String>> checkedOutModified = new HashMap<>();
+
public JcrSessionAdapter(Repository repository, ProvidedSession contentSession, Subject subject) {
this.repository = repository;
this.contentSession = contentSession;
Session session = threadSession.get(workspace);
if (session == null) {
- session = Subject.doAs(subject, (PrivilegedAction<Session>) () -> {
- try {
-// String username = CurrentUser.getUsername(subject);
-// SimpleCredentials credentials = new SimpleCredentials(username, new char[0]);
-// credentials.setAttribute(ProvidedSession.class.getName(), contentSession);
- Session sess = repository.login(workspace);
- // Jackrabbit specific:
- ((SessionImpl)sess).setAttribute(ProvidedSession.class.getName(), contentSession);
- return sess;
- } catch (RepositoryException e) {
- throw new IllegalStateException("Cannot log in to " + workspace, e);
- }
- });
+ session = login(workspace);
threadSession.put(workspace, session);
}
return session;
}
+ protected synchronized Session getWriteSession(String workspace) throws RepositoryException {
+ Session session = writeSessions.get(workspace);
+ if (session == null) {
+ session = login(workspace);
+ writeSessions.put(workspace, session);
+ } else {
+// if ((writeThread != Thread.currentThread()) && session.hasPendingChanges()) {
+// throw new IllegalStateException("Session " + contentSession + " is currently being written to");
+// }
+// writeThread = Thread.currentThread();
+ }
+ return session;
+ }
+
+ public synchronized Node openForEdit(String workspace, String jcrPath) throws RepositoryException {
+ Session session = getWriteSession(workspace);
+ Node node = session.getNode(jcrPath);
+ if (node.isNodeType(NodeType.MIX_SIMPLE_VERSIONABLE)) {
+ VersionManager versionManager = session.getWorkspace().getVersionManager();
+ if (versionManager.isCheckedOut(jcrPath)) {
+ if (!checkedOutModified.containsKey(workspace))
+ checkedOutModified.put(workspace, new TreeSet<>());
+ checkedOutModified.get(workspace).add(jcrPath);
+ } else {
+ if (!checkedInModified.containsKey(workspace))
+ checkedInModified.put(workspace, new TreeSet<>());
+ checkedInModified.get(workspace).add(jcrPath);
+ versionManager.checkout(jcrPath);
+ }
+ }
+ return node;
+ }
+
+ public synchronized void persist() throws RepositoryException {
+ for (String workspace : writeSessions.keySet()) {
+ Session session = writeSessions.get(workspace);
+ if (session == null) {
+// assert writeThread == null;
+ assert !checkedOutModified.containsKey(workspace);
+ assert !checkedInModified.containsKey(workspace);
+ return; // nothing to do
+ }
+ session.save();
+ VersionManager versionManager = session.getWorkspace().getVersionManager();
+ if (checkedOutModified.containsKey(workspace))
+ for (String jcrPath : checkedOutModified.get(workspace)) {
+ versionManager.checkpoint(jcrPath);
+ }
+ if (checkedInModified.containsKey(workspace))
+ for (String jcrPath : checkedInModified.get(workspace)) {
+ versionManager.checkin(jcrPath);
+ }
+ Jcr.logout(session);
+ }
+
+ for (Map<String, Session> m : threadSessions.values())
+ for (Session session : m.values())
+ session.refresh(true);
+// writeThread = null;
+ writeSessions.clear();
+ checkedOutModified.clear();
+ checkedInModified.clear();
+ }
+
+ protected Session login(String workspace) {
+ return Subject.doAs(subject, (PrivilegedAction<Session>) () -> {
+ try {
+// String username = CurrentUser.getUsername(subject);
+// SimpleCredentials credentials = new SimpleCredentials(username, new char[0]);
+// credentials.setAttribute(ProvidedSession.class.getName(), contentSession);
+ Session sess = repository.login(workspace);
+ // Jackrabbit specific:
+ ((SessionImpl) sess).setAttribute(ProvidedSession.class.getName(), contentSession);
+ return sess;
+ } catch (RepositoryException e) {
+ throw new IllegalStateException("Cannot log in to " + workspace, e);
+ }
+ });
+ }
+
}