View Javadoc
1   package org.argeo.people.core;
2   
3   import static org.argeo.connect.util.ConnectUtils.isEmpty;
4   import static org.argeo.connect.util.ConnectUtils.notEmpty;
5   
6   import java.text.SimpleDateFormat;
7   import java.util.ArrayList;
8   import java.util.Calendar;
9   import java.util.GregorianCalendar;
10  import java.util.List;
11  
12  import javax.jcr.Node;
13  import javax.jcr.NodeIterator;
14  import javax.jcr.Property;
15  import javax.jcr.RepositoryException;
16  import javax.jcr.Session;
17  import javax.jcr.nodetype.NodeType;
18  import javax.jcr.query.Query;
19  import javax.jcr.query.QueryResult;
20  
21  import org.apache.commons.logging.Log;
22  import org.apache.commons.logging.LogFactory;
23  import org.argeo.connect.ConnectNames;
24  import org.argeo.connect.ConnectTypes;
25  import org.argeo.connect.UserAdminService;
26  import org.argeo.connect.core.AbstractAppService;
27  import org.argeo.connect.resources.ResourcesService;
28  import org.argeo.connect.util.ConnectJcrUtils;
29  import org.argeo.connect.util.RemoteJcrUtils;
30  import org.argeo.connect.util.XPathUtils;
31  import org.argeo.jcr.JcrUtils;
32  import org.argeo.people.ContactService;
33  import org.argeo.people.PeopleConstants;
34  import org.argeo.people.PeopleException;
35  import org.argeo.people.PeopleNames;
36  import org.argeo.people.PeopleService;
37  import org.argeo.people.PeopleTypes;
38  import org.argeo.people.PersonService;
39  import org.argeo.people.util.PeopleJcrUtils;
40  import org.argeo.people.util.PersonJcrUtils;
41  import org.osgi.service.useradmin.Role;
42  import org.osgi.service.useradmin.User;
43  
44  /** Concrete access to {@link PeopleService} */
45  public class PeopleServiceImpl extends AbstractAppService implements PeopleService, PeopleNames {
46  	private final static Log log = LogFactory.getLog(PeopleServiceImpl.class);
47  
48  	/* DEPENDENCY INJECTION */
49  	private ResourcesService resourcesService;
50  	private UserAdminService userAdminService;
51  
52  	/* Centralises the various specific People services */
53  	private PersonService personService;
54  	private ContactService contactService = new ContactServiceImpl();
55  
56  	private SimpleDateFormat usersDatePath = new SimpleDateFormat("YYYY/MM");
57  
58  	@Override
59  	public Node publishEntity(Node parent, String nodeType, Node srcNode, boolean removeSrc)
60  			throws RepositoryException {
61  		if (PeopleTypes.PEOPLE_PERSON.equals(nodeType) || PeopleTypes.PEOPLE_ORG.equals(nodeType)) {
62  			String peopleUid = ConnectJcrUtils.get(srcNode, ConnectNames.CONNECT_UID);
63  			if (isEmpty(peopleUid))
64  				throw new PeopleException(
65  						"Unable to define default path for " + srcNode + ". No property people:uid is defined");
66  
67  			String relPath = getDefaultRelPath(srcNode);
68  			Node createdNode = JcrUtils.mkdirs(parent, relPath);
69  
70  			// add primary contact
71  			Node personCreatedNode = null;
72  			if (srcNode.hasNode(PeopleNames.PEOPLE_ROLE)) {
73  				Node subNode = srcNode.getNode(PeopleNames.PEOPLE_ROLE);
74  				String personRelPath = getDefaultRelPath(subNode);
75  				personCreatedNode = JcrUtils.mkdirs(parent, personRelPath);
76  				RemoteJcrUtils.copy(subNode, personCreatedNode, true);
77  				personCreatedNode.addMixin(PeopleTypes.PEOPLE_PERSON);
78  				// remove draft before copying org
79  				subNode.removeMixin(PeopleTypes.PEOPLE_PERSON);
80  				subNode.remove();
81  			}
82  
83  			RemoteJcrUtils.copy(srcNode, createdNode, true);
84  			createdNode.addMixin(nodeType);
85  			// integration with user admin
86  			configureUser(createdNode);
87  			if (personCreatedNode != null) {
88  				configureUser(personCreatedNode);
89  				JcrUtils.updateLastModified(personCreatedNode);
90  			}
91  			JcrUtils.updateLastModified(createdNode);
92  
93  			if (removeSrc) {
94  				srcNode.removeMixin(nodeType);
95  				srcNode.remove();
96  			}
97  			// must be after removing source since there is a save
98  			if (personCreatedNode != null) {
99  				personService.createOrUpdateJob(null, personCreatedNode, createdNode, null, null, true);
100 			}
101 			return createdNode;
102 		} else
103 			return null;
104 	}
105 
106 	private void configureUser(Node createdNode) throws RepositoryException {
107 		if (createdNode.hasProperty(PeopleNames.PEOPLE_USERNAME)) {
108 			createdNode.addMixin(PeopleTypes.PEOPLE_USER);
109 		} else if (createdNode.hasProperty(PeopleNames.PEOPLE_PRIMARY_EMAIL)) {
110 			String email = JcrUtils.get(createdNode, PEOPLE_PRIMARY_EMAIL);
111 			String dn = userAdminService.buildDefaultDN(email, Role.USER);
112 			User user = userAdminService.getUser(dn);
113 			if (user == null)
114 				user = userAdminService.createUserFromPerson(createdNode);
115 			createdNode.addMixin(PeopleTypes.PEOPLE_USER);
116 			createdNode.setProperty(PeopleNames.PEOPLE_USERNAME, dn);
117 		}
118 	}
119 	// private String generateUserPath(String username) {
120 	// LdapName dn;
121 	// try {
122 	// dn = new LdapName(username);
123 	// } catch (InvalidNameException e) {
124 	// throw new CmsException("Invalid name " + username, e);
125 	// }
126 	// String userId = dn.getRdn(dn.size() - 1).getValue().toString();
127 	// return usersDatePath.format(new Date()) + '/' + userId;
128 	// }
129 
130 	@Override
131 	public Node saveEntity(Node entity, boolean publish) throws PeopleException {
132 		try {
133 			if (entity.isNodeType(PeopleTypes.PEOPLE_PERSON) || entity.isNodeType(PeopleTypes.PEOPLE_ORG))
134 				entity = getPersonService().saveEntity(entity, publish);
135 			else
136 				throw new PeopleException("Unknown entity type for " + entity);
137 			return entity;
138 		} catch (RepositoryException e) {
139 			throw new PeopleException("Unable to save " + entity, e);
140 		}
141 	}
142 
143 	@Override
144 	public String getAppBaseName() {
145 		return PeopleConstants.PEOPLE_APP_BASE_NAME;
146 	}
147 
148 	@Override
149 	public String getBaseRelPath(String nodeType) {
150 		if (PeopleTypes.PEOPLE_PERSON.equals(nodeType) || ConnectTypes.CONNECT_LDAP_PERSON.equals(nodeType)
151 				|| PeopleTypes.PEOPLE_ORG.equals(nodeType) || ConnectTypes.CONNECT_LDAP_ORG.equals(nodeType)
152 				|| PeopleTypes.PEOPLE_ENTITY.equals(nodeType) || PeopleTypes.PEOPLE_CONTACTABLE.equals(nodeType))
153 			return getAppBaseName();
154 		else
155 			return null;
156 	}
157 
158 	@Override
159 	public String getDefaultRelPath(Node entity) throws RepositoryException {
160 		Calendar date;
161 		if (entity.hasProperty(Property.JCR_CREATED))
162 			date = entity.getProperty(Property.JCR_CREATED).getDate();
163 		else
164 			date = new GregorianCalendar();
165 		String creationDatePath = usersDatePath.format(date.getTime());
166 
167 		String fullName;
168 		if (entity.hasProperty(PeopleNames.PEOPLE_DISPLAY_NAME))
169 			fullName = entity.getProperty(PeopleNames.PEOPLE_DISPLAY_NAME).getString();
170 		else if (entity.hasProperty(PeopleNames.PEOPLE_LEGAL_NAME))
171 			fullName = entity.getProperty(PeopleNames.PEOPLE_LEGAL_NAME).getString();
172 		else
173 			throw new IllegalArgumentException("Cannot find full name of " + entity);
174 		String relPath = creationDatePath + "/" + fullName;
175 		return relPath;
176 		// String peopleUid = ConnectJcrUtils.get(entity, ConnectNames.CONNECT_UID);
177 		// if (isEmpty(peopleUid))
178 		// throw new PeopleException(
179 		// "Unable to define default path for " + entity + ". No property people:uid is
180 		// defined");
181 		// else
182 		// return getDefaultRelPath(ConnectJcrUtils.getSession(entity), null,
183 		// peopleUid);
184 	}
185 
186 	@Override
187 	public String getDefaultRelPath(Session session, String nodeType, String peopleUid) {
188 		// try {
189 		// return getDefaultRelPath(ConnectJcrUtils.getNodeByIdentifier(session,
190 		// peopleUid));
191 		// } catch (RepositoryException e) {
192 		// throw new ConnectException("Cannot get default relative path", e);
193 		// }
194 		String path = JcrUtils.firstCharsToPath(peopleUid, 2) + "/" + peopleUid;
195 		return path;
196 	}
197 
198 	@Override
199 	public boolean isKnownType(Node entity) {
200 		if (ConnectJcrUtils.isNodeType(entity, PeopleTypes.PEOPLE_PERSON)
201 				|| ConnectJcrUtils.isNodeType(entity, PeopleTypes.PEOPLE_ORG)
202 				|| ConnectJcrUtils.isNodeType(entity, PeopleTypes.PEOPLE_MAILING_LIST))
203 			return true;
204 		else
205 			return false;
206 	}
207 
208 	@Override
209 	public boolean isKnownType(String nodeType) {
210 		if (PeopleTypes.PEOPLE_PERSON.equals(nodeType) || PeopleTypes.PEOPLE_ORG.equals(nodeType)
211 				|| PeopleTypes.PEOPLE_MAILING_LIST.equals(nodeType))
212 			return true;
213 		else
214 			return false;
215 	}
216 
217 	@Override
218 	public String getMainNodeType(Node entity) {
219 		if (ConnectJcrUtils.isNodeType(entity, PeopleTypes.PEOPLE_PERSON))
220 			return PeopleTypes.PEOPLE_PERSON;
221 		else if (ConnectJcrUtils.isNodeType(entity, ConnectTypes.CONNECT_LDAP_PERSON))
222 			return ConnectTypes.CONNECT_LDAP_PERSON;
223 		if (ConnectJcrUtils.isNodeType(entity, PeopleTypes.PEOPLE_ORG))
224 			return PeopleTypes.PEOPLE_ORG;
225 		else if (ConnectJcrUtils.isNodeType(entity, ConnectTypes.CONNECT_LDAP_ORG))
226 			return ConnectTypes.CONNECT_LDAP_ORG;
227 		else if (ConnectJcrUtils.isNodeType(entity, PeopleTypes.PEOPLE_MAILING_LIST))
228 			return PeopleTypes.PEOPLE_MAILING_LIST;
229 		else if (ConnectJcrUtils.isNodeType(entity, PeopleTypes.PEOPLE_MOBILE))
230 			return PeopleTypes.PEOPLE_MOBILE;
231 		else if (ConnectJcrUtils.isNodeType(entity, PeopleTypes.PEOPLE_TELEPHONE_NUMBER))
232 			return PeopleTypes.PEOPLE_TELEPHONE_NUMBER;
233 		else if (ConnectJcrUtils.isNodeType(entity, PeopleTypes.PEOPLE_FAX))
234 			return PeopleTypes.PEOPLE_FAX;
235 		else if (ConnectJcrUtils.isNodeType(entity, PeopleTypes.PEOPLE_PHONE))
236 			return PeopleTypes.PEOPLE_PHONE;
237 		else if (ConnectJcrUtils.isNodeType(entity, PeopleTypes.PEOPLE_MAIL))
238 			return PeopleTypes.PEOPLE_MAIL;
239 		else if (ConnectJcrUtils.isNodeType(entity, PeopleTypes.PEOPLE_URL))
240 			return PeopleTypes.PEOPLE_URL;
241 		else if (ConnectJcrUtils.isNodeType(entity, PeopleTypes.PEOPLE_SOCIAL_MEDIA))
242 			return PeopleTypes.PEOPLE_SOCIAL_MEDIA;
243 		else if (ConnectJcrUtils.isNodeType(entity, PeopleTypes.PEOPLE_IMPP))
244 			return PeopleTypes.PEOPLE_IMPP;
245 		else if (ConnectJcrUtils.isNodeType(entity, PeopleTypes.PEOPLE_POSTAL_ADDRESS))
246 			return PeopleTypes.PEOPLE_POSTAL_ADDRESS;
247 		else if (ConnectJcrUtils.isNodeType(entity, PeopleTypes.PEOPLE_CONTACT))
248 			return PeopleTypes.PEOPLE_CONTACT;
249 		else if (ConnectJcrUtils.isNodeType(entity, PeopleTypes.PEOPLE_JOB))
250 			return PeopleTypes.PEOPLE_JOB;
251 		else if (ConnectJcrUtils.isNodeType(entity, PeopleTypes.PEOPLE_POSITION))
252 			return PeopleTypes.PEOPLE_POSITION;
253 		else if (ConnectJcrUtils.isNodeType(entity, PeopleTypes.PEOPLE_BANK_ACCOUNT))
254 			return PeopleTypes.PEOPLE_BANK_ACCOUNT;
255 		else if (ConnectJcrUtils.isNodeType(entity, PeopleTypes.PEOPLE_MEMBER))
256 			return PeopleTypes.PEOPLE_MEMBER;
257 		else
258 			return null;
259 	}
260 
261 	@Override
262 	public String getDisplayName(Node entity) {
263 		String displayName = null;
264 		try {
265 			if (entity.hasProperty(PEOPLE_DISPLAY_NAME))
266 				displayName = entity.getProperty(PEOPLE_DISPLAY_NAME).getString();
267 			else if (entity.isNodeType(PeopleTypes.PEOPLE_PERSON))
268 				displayName = getPersonService().getDefaultDisplayName(entity);
269 			else if (entity.isNodeType(NodeType.MIX_TITLE))
270 				displayName = ConnectJcrUtils.get(entity, Property.JCR_TITLE);
271 			else
272 				throw new PeopleException("Display name not defined for type " + entity.getPrimaryNodeType().getName()
273 						+ " - node: " + entity);
274 		} catch (RepositoryException e) {
275 			throw new PeopleException("Unable to get display name for node " + entity, e);
276 		}
277 		return displayName;
278 	}
279 
280 	/**
281 	 * Simply looks for primary information and updates the primary cache if needed
282 	 */
283 	@Override
284 	public void updatePrimaryCache(Node entity) throws PeopleException, RepositoryException {
285 		if (ConnectJcrUtils.isNodeType(entity, PeopleTypes.PEOPLE_PERSON)
286 				|| ConnectJcrUtils.isNodeType(entity, PeopleTypes.PEOPLE_ORG)) {
287 			for (String currType : PeopleTypes.KNOWN_CONTACT_TYPES) {
288 				Node pNode = PeopleJcrUtils.getPrimaryContact(entity, currType);
289 				if (pNode != null)
290 					PeopleJcrUtils.updatePrimaryCache(resourcesService, this, entity, pNode, true);
291 			}
292 
293 			// Also update primary job
294 			if (ConnectJcrUtils.isNodeType(entity, PeopleTypes.PEOPLE_PERSON)) {
295 				Node pJob = PersonJcrUtils.getPrimaryJob(entity);
296 				if (pJob != null)
297 					PeopleJcrUtils.updatePrimaryCache(resourcesService, this, entity, pJob, true);
298 			}
299 		} else
300 			log.warn("Trying to update primary cache on " + entity + " - Unknown type.");
301 	}
302 
303 	@Override
304 	public Node getEntityFromNodeReference(Node node, String propName) {
305 		try {
306 			String peopleUid = ConnectJcrUtils.get(node, propName);
307 			if (isEmpty(peopleUid))
308 				return null;
309 			else
310 				return getEntityByUid(node.getSession(), null, peopleUid);
311 		} catch (RepositoryException re) {
312 			throw new PeopleException(
313 					"unable to get entity from reference node " + node + " with ref property " + propName, re);
314 		}
315 	}
316 
317 	/**
318 	 * Creates and returns a model specific Node to store a reference, depending on
319 	 * the two object we want to link together. Overwrite to add some new link type
320 	 */
321 	@Override
322 	public Node createEntityReference(Node referencingNode, Node referencedNode, String role) {
323 		try {
324 			Node parentNode = null;
325 			String linkNodeType = null;
326 			if (referencingNode.isNodeType(PeopleTypes.PEOPLE_PERSON)) {
327 				if (referencedNode.isNodeType(PeopleTypes.PEOPLE_ORG)) {
328 					linkNodeType = PeopleTypes.PEOPLE_JOB;
329 					parentNode = referencingNode.getNode(PeopleNames.PEOPLE_JOBS);
330 				}
331 			} else if (referencingNode.isNodeType(PeopleTypes.PEOPLE_GROUP)) {
332 				if (referencedNode.isNodeType(PeopleTypes.PEOPLE_ORG)
333 						|| referencedNode.isNodeType(PeopleTypes.PEOPLE_PERSON)
334 						|| (referencedNode.isNodeType(PeopleTypes.PEOPLE_GROUP)
335 								&& referencedNode.getIdentifier() != referencingNode.getIdentifier())) {
336 					linkNodeType = PeopleTypes.PEOPLE_MEMBER;
337 					parentNode = referencingNode.getNode(PeopleNames.PEOPLE_MEMBERS);
338 				}
339 			}
340 			if (parentNode == null || linkNodeType == null)
341 				throw new PeopleException(
342 						"Unsupported reference: from " + referencingNode + "(" + referencingNode.getPrimaryNodeType()
343 								+ ")" + " to " + referencedNode + "(" + referencedNode.getPrimaryNodeType() + ")");
344 
345 			// Legacy: force node to be checked-out
346 			if (!ConnectJcrUtils.checkCOStatusBeforeUpdate(referencingNode))
347 				log.warn("Referencing node " + referencingNode + " was checked in when we wanted to update");
348 
349 			Node link = parentNode.addNode(notEmpty(role) ? role : "Unnamed_role", linkNodeType);
350 			if (notEmpty(role))
351 				link.setProperty(PeopleNames.PEOPLE_ROLE, role);
352 			link.setProperty(PeopleNames.PEOPLE_REF_UID,
353 					referencedNode.getProperty(ConnectNames.CONNECT_UID).getString());
354 			referencingNode.getSession().save();
355 
356 			return link;
357 		} catch (RepositoryException e) {
358 			throw new PeopleException("Unable to retrieve related entities", e);
359 		}
360 	}
361 
362 	@Override
363 	public List<Node> getRelatedEntities(Node entity, String linkNodeType, String relatedEntityType) {
364 		try {
365 			if (!entity.hasProperty(ConnectNames.CONNECT_UID))
366 				return null;
367 			String xpathQueryStr = "//element(*, " + linkNodeType + ")";
368 			String attrQuery = XPathUtils.getPropertyEquals(PeopleNames.PEOPLE_REF_UID,
369 					entity.getProperty(ConnectNames.CONNECT_UID).getString());
370 			if (notEmpty(attrQuery))
371 				xpathQueryStr += "[" + attrQuery + "]";
372 			Query xpathQuery = XPathUtils.createQuery(entity.getSession(), xpathQueryStr);
373 			QueryResult result = xpathQuery.execute();
374 			NodeIterator ni = result.getNodes();
375 			if (relatedEntityType == null)
376 				return JcrUtils.nodeIteratorToList(ni);
377 			else {
378 				List<Node> cleaned = new ArrayList<Node>();
379 				while (ni.hasNext()) {
380 					Node currNode = ni.nextNode();
381 					if (currNode.getParent().getParent().isNodeType(relatedEntityType))
382 						cleaned.add(currNode);
383 				}
384 				return cleaned;
385 			}
386 		} catch (RepositoryException e) {
387 			throw new PeopleException("Unable to retrieve " + linkNodeType + " related "
388 					+ (relatedEntityType == null ? "" : relatedEntityType) + " entities for " + entity, e);
389 		}
390 	}
391 
392 	/* EXPOSED SERVICES */
393 	@Override
394 	public PersonService getPersonService() {
395 		return personService;
396 	}
397 
398 	@Override
399 	public ContactService getContactService() {
400 		return contactService;
401 	}
402 
403 	/* LIFE CYCLE MANAGEMENT */
404 	/**
405 	 * Call by each startup in order to make sure the backend is ready to
406 	 * receive/provide data.
407 	 */
408 	@Deprecated
409 	public void init() {
410 		personService = new PersonServiceImpl(this, resourcesService);
411 		if (log.isDebugEnabled())
412 			log.info("People's backend has been initialized");
413 	}
414 
415 	/** Clean shutdown of the backend. */
416 	public void destroy() {
417 	}
418 
419 	/* DEPENDENCY INJECTION */
420 	public void setResourcesService(ResourcesService resourcesService) {
421 		this.resourcesService = resourcesService;
422 		personService = new PersonServiceImpl(this, resourcesService);
423 	}
424 
425 	public void setUserAdminService(UserAdminService userAdminService) {
426 		this.userAdminService = userAdminService;
427 	}
428 
429 }