View Javadoc
1   package org.argeo.connect.core;
2   
3   import java.util.UUID;
4   
5   import javax.jcr.Node;
6   import javax.jcr.NodeIterator;
7   import javax.jcr.Property;
8   import javax.jcr.RepositoryException;
9   import javax.jcr.Session;
10  import javax.jcr.nodetype.NodeType;
11  import javax.jcr.query.Query;
12  
13  import org.apache.commons.logging.Log;
14  import org.apache.commons.logging.LogFactory;
15  import org.argeo.connect.AppService;
16  import org.argeo.connect.ConnectConstants;
17  import org.argeo.connect.ConnectException;
18  import org.argeo.connect.ConnectNames;
19  import org.argeo.connect.ConnectTypes;
20  import org.argeo.connect.util.ConnectJcrUtils;
21  import org.argeo.connect.util.RemoteJcrUtils;
22  import org.argeo.connect.util.XPathUtils;
23  import org.argeo.eclipse.ui.EclipseUiUtils;
24  import org.argeo.jcr.JcrUtils;
25  import org.argeo.node.NodeUtils;
26  
27  public abstract class AbstractAppService implements AppService {
28  	private final static Log log = LogFactory.getLog(AbstractAppService.class);
29  
30  	public Node publishEntity(Node parent, String nodeType, Node srcNode, boolean removeSrcNode)
31  			throws RepositoryException {
32  		Node createdNode = null;
33  		if (isKnownType(nodeType)) {
34  			String relPath = getDefaultRelPath(srcNode);
35  
36  			if (parent == null) {
37  				StackTraceElement[] stack = Thread.currentThread().getStackTrace();
38  				StringBuilder builder = new StringBuilder();
39  				builder.append("Trying to publish a node with no parent, "
40  						+ "this approach will soon be forbidden. Calling stack:\n");
41  				for (StackTraceElement el : stack) {
42  					builder.append(el.toString() + "\n");
43  				}
44  				log.error(builder.toString());
45  				parent = srcNode.getSession().getNode("/" + getBaseRelPath(nodeType));
46  			}
47  
48  			// TODO check duplicate
49  			String parRelPath = ConnectJcrUtils.parentRelPath(relPath);
50  
51  			Node tmpParent = null;
52  			if (EclipseUiUtils.isEmpty(parRelPath))
53  				tmpParent = parent;
54  			else
55  				tmpParent = JcrUtils.mkdirs(parent, parRelPath);
56  
57  			createdNode = tmpParent.addNode(ConnectJcrUtils.lastRelPathElement(relPath));
58  
59  			RemoteJcrUtils.copy(srcNode, createdNode, true);
60  			createdNode.addMixin(nodeType);
61  			JcrUtils.updateLastModified(createdNode);
62  			if (removeSrcNode)
63  				srcNode.remove();
64  		}
65  		return createdNode;
66  	}
67  
68  	/**
69  	 * Try to save and optionally publish a business object after applying context
70  	 * specific rules and special behaviours (typically cache updates).
71  	 * 
72  	 * @return the entity that has been saved (and optionally published): note that
73  	 *         in some cases (typically, the first save of a draft node in the
74  	 *         business sub tree) the returned node is not the same as the one that
75  	 *         has been passed
76  	 * @param entity
77  	 * @param publish
78  	 *            also publishes the corresponding node
79  	 * @throws PeopleException
80  	 *             If one of the rule defined for this type is not respected. Use
81  	 *             getMessage to display to the user if needed
82  	 */
83  	public Node saveEntity(Node entity, boolean publish) {
84  		try {
85  			Session session = entity.getSession();
86  			if (session.hasPendingChanges()) {
87  				JcrUtils.updateLastModified(entity);
88  				session.save();
89  			}
90  			if (entity.isNodeType(NodeType.MIX_VERSIONABLE))
91  				// TODO check if some changes happened since last checkpoint
92  				session.getWorkspace().getVersionManager().checkpoint(entity.getPath());
93  			return entity;
94  		} catch (RepositoryException e) {
95  			throw new ConnectException("Cannot save " + entity, e);
96  		}
97  	}
98  
99  	/**
100 	 * Returns a display name that is app specific and that depends on one or more
101 	 * of the entity properties. The user can always set a flag to force the value
102 	 * to something else.
103 	 * 
104 	 * The Display name is usually stored in the JCR_TITLE property.
105 	 */
106 	public String getDisplayName(Node entity) {
107 		String defaultDisplayName = ConnectJcrUtils.get(entity, Property.JCR_TITLE);
108 		if (defaultDisplayName == null || "".equals(defaultDisplayName.trim()))
109 			return ConnectJcrUtils.getName(entity);
110 		else
111 			return defaultDisplayName;
112 	}
113 
114 	/**
115 	 * Returns (after creation if necessary) the base parent for draft nodes of this
116 	 * application
117 	 */
118 	public Node getDraftParent(Session session) throws RepositoryException {
119 		Node home = NodeUtils.getUserHome(session);
120 		String draftRelPath = ConnectConstants.HOME_APP_SYS_RELPARPATH + "/" + getAppBaseName();
121 		return JcrUtils.mkdirs(home, draftRelPath);
122 	}
123 
124 	/**
125 	 * Convenience method to create a Node with given mixin under the current logged
126 	 * in user home. Creates a UUID and set the connect:uid properties. The session
127 	 * is not saved.
128 	 */
129 	public Node createDraftEntity(Session session, String mainMixin) throws RepositoryException {
130 		Node parent = getDraftParent(session);
131 		String connectUid = UUID.randomUUID().toString();
132 		Node draftNode = parent.addNode(connectUid);
133 		draftNode.addMixin(mainMixin);
134 		draftNode.setProperty(ConnectNames.CONNECT_UID, connectUid);
135 		return draftNode;
136 	}
137 
138 	/**
139 	 * Searches the workspace corresponding to the passed session. It returns the
140 	 * corresponding entity or null if none has been found. This UID is
141 	 * implementation specific and is not a JCR Identifier.
142 	 * 
143 	 * It will throw a PeopleException if more than one item with this ID has been
144 	 * found
145 	 * 
146 	 * @param session
147 	 * @param parentPath
148 	 *            can be null or empty
149 	 * @param uid
150 	 *            the implementation specific UID of the searched entity
151 	 */
152 	public Node getEntityByUid(Session session, String parentPath, String uid) {
153 		if (uid == null || "".equals(uid.trim()))
154 			throw new ConnectException("uid cannot be null or empty");
155 		try {
156 			StringBuilder builder = new StringBuilder();
157 			builder.append(XPathUtils.descendantFrom(parentPath));
158 			builder.append("//element(*, ").append(ConnectTypes.CONNECT_ENTITY).append(")");
159 			builder.append("[").append(XPathUtils.getPropertyEquals(ConnectNames.CONNECT_UID, uid)).append("]");
160 			Query xpathQuery = XPathUtils.createQuery(session, builder.toString());
161 			NodeIterator ni = xpathQuery.execute().getNodes();
162 			long niSize = ni.getSize();
163 			if (niSize == 0)
164 				return null;
165 			else if (niSize > 1) {
166 				// TODO rather include the calling stack in the thrown
167 				// ConnectException
168 				log.error("Found " + niSize + " entities with connect:uid [" + uid + "] - calling stack:\n "
169 						+ Thread.currentThread().getStackTrace().toString());
170 				Node first = ni.nextNode();
171 				throw new ConnectException(
172 						"Found " + niSize + " entities for People UID [" + uid + "], First occurence info:\npath: "
173 								+ first.getPath() + ", node type: " + first.getPrimaryNodeType().getName() + "");
174 			} else
175 				return ni.nextNode();
176 		} catch (RepositoryException e) {
177 			throw new ConnectException("Unable to retrieve entity with connect:uid  [" + uid + "]", e);
178 		}
179 	}
180 
181 }