View Javadoc
1   package org.argeo.people.util;
2   
3   import static org.argeo.connect.util.ConnectUtils.isEmpty;
4   import static org.argeo.connect.util.ConnectUtils.notEmpty;
5   
6   import java.io.InputStream;
7   import java.util.ArrayList;
8   import java.util.Calendar;
9   import java.util.Dictionary;
10  import java.util.List;
11  
12  import javax.jcr.Binary;
13  import javax.jcr.Node;
14  import javax.jcr.NodeIterator;
15  import javax.jcr.Property;
16  import javax.jcr.RepositoryException;
17  import javax.jcr.Session;
18  import javax.jcr.nodetype.NodeType;
19  
20  import org.argeo.connect.AppService;
21  import org.argeo.connect.ConnectConstants;
22  import org.argeo.connect.ConnectNames;
23  import org.argeo.connect.ConnectTypes;
24  import org.argeo.connect.resources.ResourcesService;
25  import org.argeo.connect.util.ConnectJcrUtils;
26  import org.argeo.jcr.JcrUtils;
27  import org.argeo.naming.LdapAttrs;
28  import org.argeo.naming.NamingUtils;
29  import org.argeo.node.NodeUtils;
30  import org.argeo.people.ContactValueCatalogs;
31  import org.argeo.people.PeopleException;
32  import org.argeo.people.PeopleNames;
33  import org.argeo.people.PeopleService;
34  import org.argeo.people.PeopleTypes;
35  import org.osgi.service.useradmin.User;
36  
37  /**
38   * Static utility methods to manage generic CRM concepts within JCR. Rather use
39   * these methods than direct JCR queries in order to ease model evolution.
40   */
41  public class PeopleJcrUtils implements PeopleNames {
42  	// private final static Log log = LogFactory.getLog(PeopleJcrUtils.class);
43  
44  	/**
45  	 * @deprecated Use {@link PersonJcrUtils#getPersonWithUsername(Session, String)}
46  	 */
47  	@Deprecated
48  	public static Node getProfile(Session session, String username) throws RepositoryException {
49  		// FIXME search instead
50  		if (username == null)
51  			username = session.getUserID();
52  		Node userHome = NodeUtils.getUserHome(session, username);
53  		Node profile = null;
54  		NodeIterator children = userHome.getNodes();
55  		while (children.hasNext()) {
56  			Node child = children.nextNode();
57  			if (child.isNodeType(PeopleTypes.PEOPLE_PERSON)) {
58  				profile = child;
59  				break;
60  			}
61  		}
62  		return profile;
63  	}
64  
65  	/* GROUP MANAGEMENT */
66  	/**
67  	 * Add an entity to a given group
68  	 * 
69  	 * @param role
70  	 *            the role of the given entity in this group. Cannot be null
71  	 * @param title
72  	 *            OPTIONAL: the nature of the subject in this relation, for instance
73  	 *            "Actor" or "Engineer"
74  	 */
75  	public static Node addEntityToGroup(Node group, Node entity, String role, String title, Calendar dateBegin,
76  			Calendar dateEnd, Boolean isCurrent) throws RepositoryException {
77  
78  		Node members = JcrUtils.mkdirs(group, PEOPLE_MEMBERS, NodeType.NT_UNSTRUCTURED);
79  		Node member = members.addNode(ConnectJcrUtils.get(entity, Property.JCR_TITLE), PeopleTypes.PEOPLE_MEMBER);
80  		member.addMixin(PeopleTypes.PEOPLE_CONTACT_REF);
81  		member.setProperty(PEOPLE_REF_UID, ConnectJcrUtils.get(entity, ConnectNames.CONNECT_UID));
82  		member.setProperty(PEOPLE_REF_TITLE, ConnectJcrUtils.get(entity, Property.JCR_TITLE));
83  		member.setProperty(PEOPLE_ROLE, role);
84  		if (notEmpty(title))
85  			throw new PeopleException("Position Nature: Unimplemented property ");
86  		// member.setProperty(Property.JCR_TITLE, title);
87  		if (dateBegin != null)
88  			member.setProperty(PEOPLE_DATE_BEGIN, dateBegin);
89  		if (dateEnd != null)
90  			member.setProperty(PEOPLE_DATE_END, dateEnd);
91  		if (isCurrent != null)
92  			member.setProperty(PEOPLE_IS_CURRENT, isCurrent);
93  		return member;
94  	}
95  
96  	public static void setContactCategory(Node contactNode, String contactType, String category)
97  			throws RepositoryException {
98  		if (isEmpty(category))
99  			category = ContactValueCatalogs.MAPS_CONTACT_TYPES.get(contactType);
100 		contactNode.setProperty(Property.JCR_TITLE, category);
101 	}
102 
103 	/**
104 	 * Marks a contact as pro if orga in not null by adding a mixin and setting
105 	 * corresponding values. if orga is null and the contact is marked as pro, it
106 	 * will remove the mixin and the corresponding properties
107 	 * 
108 	 * @param contactNode
109 	 * @param orga
110 	 * @throws RepositoryException
111 	 */
112 	public static void markAsPro(Node contactNode, Node orga) throws RepositoryException {
113 		if (orga != null) {
114 			contactNode.setProperty(PEOPLE_REF_UID, ConnectJcrUtils.get(orga, ConnectNames.CONNECT_UID));
115 			contactNode.setProperty(PEOPLE_REF_TITLE, ConnectJcrUtils.get(orga, Property.JCR_TITLE));
116 		} else {
117 			if (ConnectJcrUtils.isNodeType(contactNode, PeopleTypes.PEOPLE_CONTACT_REF))
118 				contactNode.removeMixin(PeopleTypes.PEOPLE_CONTACT_REF);
119 			if (contactNode.hasProperty(PeopleNames.PEOPLE_REF_UID))
120 				contactNode.getProperty(PeopleNames.PEOPLE_REF_UID).remove();
121 			if (contactNode.hasProperty(PeopleNames.PEOPLE_REF_TITLE))
122 				contactNode.getProperty(PeopleNames.PEOPLE_REF_TITLE).remove();
123 		}
124 	}
125 
126 	/**
127 	 * Returns the country of an entity relying on its primary address, if defined
128 	 */
129 	public static String getCountryFromItem(PeopleService peopleService, Node contactable) {
130 		Node primContact = getPrimaryContact(contactable, PeopleTypes.PEOPLE_POSTAL_ADDRESS);
131 		if (primContact != null && ConnectJcrUtils.isNodeType(primContact, PeopleTypes.PEOPLE_CONTACT_REF)) {
132 			// retrieve primary address for the referenced Node
133 			Node referenced = peopleService.getEntityFromNodeReference(primContact, PEOPLE_REF_UID);
134 			if (referenced != null)
135 				primContact = getPrimaryContact(referenced, PeopleTypes.PEOPLE_POSTAL_ADDRESS);
136 		}
137 		if (primContact != null)
138 			return ConnectJcrUtils.get(primContact, PEOPLE_COUNTRY);
139 		return "";
140 	}
141 
142 	/**
143 	 * Returns the country of an entity relying on its primary address, if defined
144 	 */
145 	public static String getTownFromItem(PeopleService peopleService, Node contactable) {
146 		Node node = getPrimaryContact(contactable, PeopleTypes.PEOPLE_POSTAL_ADDRESS);
147 		if (node != null && ConnectJcrUtils.isNodeType(node, PeopleTypes.PEOPLE_CONTACT_REF)) {
148 			// retrieve primary address for the referenced Node
149 			Node referenced = peopleService.getEntityFromNodeReference(node, PEOPLE_REF_UID);
150 			if (referenced != null)
151 				node = getPrimaryContact(referenced, PeopleTypes.PEOPLE_POSTAL_ADDRESS);
152 		}
153 		if (node != null)
154 			return ConnectJcrUtils.get(node, PEOPLE_CITY);
155 		return "";
156 	}
157 
158 	/**
159 	 * Returns the primary contact for the type or null if no node with this type is
160 	 * defined as primary
161 	 */
162 	public static Node getPrimaryContact(Node contactable, String contactType) {
163 		try {
164 			if (contactable.hasNode(PEOPLE_CONTACTS)) {
165 				Node contacts = contactable.getNode(PEOPLE_CONTACTS);
166 				NodeIterator ni = contacts.getNodes();
167 				while (ni.hasNext()) {
168 					Node currNode = ni.nextNode();
169 					if (currNode.isNodeType(contactType) && currNode.hasProperty(PEOPLE_IS_PRIMARY)) {
170 						if (currNode.getProperty(PEOPLE_IS_PRIMARY).getBoolean())
171 							return currNode;
172 					}
173 				}
174 			}
175 			return null;
176 		} catch (RepositoryException re) {
177 			throw new PeopleException("Unable to get primary contact of type " + contactType + " for " + contactable,
178 					re);
179 		}
180 	}
181 
182 	/** Returns a list of contact for the given entity and type */
183 	public static List<Node> getContactOfType(Node contactable, String contactType) {
184 		List<Node> result = new ArrayList<Node>();
185 		try {
186 			if (contactable.hasNode(PEOPLE_CONTACTS)) {
187 				Node contacts = contactable.getNode(PEOPLE_CONTACTS);
188 				NodeIterator ni = contacts.getNodes();
189 				while (ni.hasNext()) {
190 					Node currNode = ni.nextNode();
191 					if (currNode.isNodeType(contactType)) {
192 						result.add(currNode);
193 					}
194 				}
195 			}
196 		} catch (RepositoryException re) {
197 			throw new PeopleException("Unable to get contact list of type " + contactType + " for " + contactable, re);
198 		}
199 		return result;
200 	}
201 
202 	/**
203 	 * Returns the primary contact value given a type or an empty String if no node
204 	 * with this type is defined as primary
205 	 * 
206 	 * @param contactable
207 	 * @param contactType
208 	 * @return
209 	 */
210 	public static String getPrimaryContactValue(Node contactable, String contactType) {
211 		Node primary = getPrimaryContact(contactable, contactType);
212 		if (primary != null)
213 			return ConnectJcrUtils.get(primary, PEOPLE_CONTACT_VALUE);
214 		else
215 			return "";
216 	}
217 
218 	/**
219 	 * Marks the given node as primary using people specific mechanism, that is
220 	 * 
221 	 * <ul>
222 	 * <li>it moves the corresponding node as first childnode of this node's
223 	 * parent</li>
224 	 * <li>if this node has people:orderable mixin, it sets the isPrimary flag to
225 	 * true for this node and to false for all siblings that have the same node
226 	 * type</li>
227 	 * <li>it returns true only if some changes have been performed.</li>
228 	 * </ul>
229 	 */
230 	public static boolean markAsPrimary(ResourcesService resourcesService, PeopleService peopleService, Node parentNode,
231 			Node primaryChild) {
232 		try {
233 			Node parent = primaryChild.getParent();
234 			String thisNodeType = peopleService.getMainNodeType(primaryChild);
235 
236 			if (PeopleTypes.PEOPLE_CONTACT.equals(thisNodeType))
237 				throw new PeopleException("Unknown node type for " + primaryChild
238 						+ ", cannot be maked as primary.\n Mixin for this node: "
239 						+ primaryChild.getMixinNodeTypes().toString());
240 
241 			if (isPrimary(parentNode, primaryChild)) {
242 				primaryChild.setProperty(PeopleNames.PEOPLE_IS_PRIMARY, false);
243 				return true;
244 			}
245 
246 			NodeIterator ni = parent.getNodes();
247 			Node firstNode = ni.nextNode();
248 			// update primary flag if needed
249 			// if (primaryChild.isNodeType(PeopleTypes.PEOPLE_ORDERABLE)) {
250 			ni = parent.getNodes();
251 			while (ni.hasNext()) {
252 				Node nextNode = ni.nextNode();
253 				if (nextNode.isNodeType(thisNodeType) && !primaryChild.getIdentifier().equals(nextNode.getIdentifier()))
254 					nextNode.setProperty(PeopleNames.PEOPLE_IS_PRIMARY, false);
255 			}
256 			primaryChild.setProperty(PeopleNames.PEOPLE_IS_PRIMARY, true);
257 
258 			// move first
259 			parent.orderBefore(JcrUtils.lastPathElement(primaryChild.getPath()),
260 					JcrUtils.lastPathElement(firstNode.getPath()));
261 
262 			updatePrimaryCache(resourcesService, peopleService, parentNode, primaryChild, true);
263 			return true;
264 		} catch (RepositoryException re) {
265 			throw new PeopleException("Unable to mark " + primaryChild + " as primary", re);
266 		}
267 	}
268 
269 	/** Check if current child is primary */
270 	public static boolean isPrimary(Node parentNode, Node primaryChild) {
271 		try {
272 			if (primaryChild.hasProperty(PEOPLE_IS_PRIMARY) && primaryChild.getProperty(PEOPLE_IS_PRIMARY).getBoolean())
273 				return true;
274 
275 			return false;
276 
277 			// TODO rather only rely on nodes order
278 			// Node parent = primaryChild.getParent();
279 			// String thisNodeType =
280 			// primaryChild.getPrimaryNodeType().getName();
281 			// // Check if something must change
282 			// NodeIterator ni = parent.getNodes();
283 			// while (ni.hasNext()) {
284 			// Node nextNode = ni.nextNode();
285 			// if (primaryChild.getIdentifier().equals(
286 			// nextNode.getIdentifier()))
287 			// return true;
288 			// else if (nextNode.isNodeType(thisNodeType))
289 			// return false;
290 			// }
291 			// throw new PeopleException("We should have found current "
292 			// + "node and never reach this point");
293 		} catch (RepositoryException re) {
294 			throw new PeopleException("Unable to check primary status for " + primaryChild, re);
295 		}
296 	}
297 
298 	/**
299 	 * After setting a given node as primary, it tries to update parent node
300 	 * corresponding cache properties. This properties are mainly used to enable
301 	 * full text search with no join.
302 	 * 
303 	 * If isPrimary = false e.g. corresponding child will be deleted, corresponding
304 	 * properties are removed.
305 	 * 
306 	 * Updated cache properties depend on the primary node type
307 	 */
308 	public static void updatePrimaryCache(ResourcesService resourcesService, PeopleService peopleService,
309 			Node parentNode, Node primaryChild, boolean isPrimary) {
310 		try {
311 			if (primaryChild.isNodeType(PeopleTypes.PEOPLE_MOBILE))
312 				internalUpdatePrimCache(parentNode, primaryChild, PEOPLE_PMOBILE, isPrimary);
313 			else if (primaryChild.isNodeType(PeopleTypes.PEOPLE_TELEPHONE_NUMBER))
314 				internalUpdatePrimCache(parentNode, primaryChild, PEOPLE_PTELEPHONE_NUMBER, isPrimary);
315 			else if (primaryChild.isNodeType(PeopleTypes.PEOPLE_MAIL))
316 				internalUpdatePrimCache(parentNode, primaryChild, PEOPLE_PMAIL, isPrimary);
317 			else if (primaryChild.isNodeType(PeopleTypes.PEOPLE_URL))
318 				internalUpdatePrimCache(parentNode, primaryChild, PEOPLE_PURL, isPrimary);
319 			else if (primaryChild.isNodeType(PeopleTypes.PEOPLE_POSTAL_ADDRESS))
320 				internalUpdatePrimCache(parentNode, primaryChild, PEOPLE_PPOSTAL_ADDRESS, isPrimary);
321 			// {
322 			// if (isPrimary) {
323 			// String cityStr = "", countryStr = "";
324 			// if (primaryChild.isNodeType(PeopleTypes.PEOPLE_CONTACT_REF)
325 			// && notEmpty(ConnectJcrUtils.get(primaryChild,
326 			// PEOPLE_REF_UID))) {
327 			// Node linkedOrg =
328 			// peopleService.getEntityFromNodeReference(primaryChild,
329 			// PEOPLE_REF_UID);
330 			// if (linkedOrg != null) {
331 			// cityStr = getTownFromItem(peopleService, linkedOrg);
332 			// countryStr = getCountryFromItem(peopleService, linkedOrg);
333 			// }
334 			// } else {
335 			// cityStr = ConnectJcrUtils.get(primaryChild, PEOPLE_CITY);
336 			// countryStr = ConnectJcrUtils.get(primaryChild, PEOPLE_COUNTRY);
337 			// if (notEmpty(countryStr))
338 			// countryStr =
339 			// resourcesService.getEncodedTagValue(ConnectJcrUtils.getSession(primaryChild),
340 			// ConnectConstants.RESOURCE_COUNTRY, countryStr);
341 			//
342 			// }
343 			//
344 			// List<String> localisation = new ArrayList<>();
345 			// if (notEmpty(cityStr))
346 			// localisation.add(cityStr);
347 			// if (notEmpty(cityStr))
348 			// localisation.add(cityStr);
349 			// if (!localisation.isEmpty())
350 			// parentNode.setProperty(PEOPLE_PPOSTAL_ADDRESS,
351 			// localisation.toArray(new String[0]));
352 			// else if (parentNode.hasProperty(PEOPLE_PPOSTAL_ADDRESS))
353 			// parentNode.getProperty(PEOPLE_PPOSTAL_ADDRESS).remove();
354 			//
355 			// } else if (parentNode.hasProperty(PEOPLE_PPOSTAL_ADDRESS))
356 			// parentNode.getProperty(PEOPLE_PPOSTAL_ADDRESS).remove();
357 			// }
358 			else if (primaryChild.isNodeType(PeopleTypes.PEOPLE_JOB)) {
359 				if (isPrimary) {
360 					Node linkedOrg = peopleService.getEntityFromNodeReference(primaryChild, PEOPLE_REF_UID);
361 					if (linkedOrg != null) {
362 						parentNode.setProperty(PEOPLE_PORG, ConnectJcrUtils.get(linkedOrg, Property.JCR_TITLE));
363 					}
364 				} else {
365 					if (parentNode.hasProperty(PEOPLE_PORG))
366 						parentNode.setProperty(PEOPLE_PORG, "");
367 				}
368 			}
369 		} catch (RepositoryException re) {
370 			throw new PeopleException("Unable to mark " + primaryChild + " as primary", re);
371 		}
372 	}
373 
374 	private static void internalUpdatePrimCache(Node parentNode, Node primaryChild, String cachePropertyName,
375 			boolean isPrimary) throws RepositoryException {
376 		internalUpdatePrimCache(parentNode, primaryChild, PEOPLE_CONTACT_VALUE, cachePropertyName, isPrimary);
377 	}
378 
379 	private static void internalUpdatePrimCache(Node parentNode, Node primaryChild, String valuePropertyName,
380 			String cachePropertyName, boolean isPrimary) throws RepositoryException {
381 		if (isPrimary)
382 			parentNode.setProperty(cachePropertyName, ConnectJcrUtils.get(primaryChild, valuePropertyName));
383 		else if (parentNode.hasProperty(cachePropertyName))
384 			parentNode.getProperty(cachePropertyName).remove();
385 	}
386 
387 	/**
388 	 * Create a contact node and add basic info
389 	 * 
390 	 * @param contactable
391 	 * @param contactType
392 	 * @param value
393 	 * @param isPrimary
394 	 * @param title
395 	 *            business type of the current contact, for instance for social
396 	 *            media, tweeter, linkedin, ... or other
397 	 * @param description
398 	 *            an optional description
399 	 * @param name
400 	 * 
401 	 * @return
402 	 */
403 
404 	public static Node createContact(ResourcesService resourcesService, PeopleService peopleService, Node contactable,
405 			String contactType, String value, boolean isPrimary, String title, String description) {
406 		return createContact(resourcesService, peopleService, contactable, contactType, value, isPrimary, null, title,
407 				description);
408 	}
409 
410 	/**
411 	 * Create a contact node and add basic info
412 	 * 
413 	 * @param contactable
414 	 * @param contactType
415 	 * @param value
416 	 * @param isPrimary
417 	 * @param linkedOrg
418 	 *            if nature = pro, then we can pass an optional organisation linked
419 	 *            to this contact
420 	 * @param title
421 	 *            business type of the current contact, for instance for social
422 	 *            media, tweeter, linkedin, ... or other
423 	 * @param description
424 	 *            an optional label
425 	 * @param name
426 	 * @param nature
427 	 *            for a person related contact, pro or private
428 	 * 
429 	 * @return
430 	 */
431 	public static Node createContact(ResourcesService resourcesService, PeopleService peopleService, Node contactable,
432 			String contactType, String value, boolean isPrimary, Node linkedOrg, String title, String description) {
433 		try {
434 			Node contacts = JcrUtils.mkdirs(contactable, PEOPLE_CONTACTS, NodeType.NT_UNSTRUCTURED);
435 			String name = ContactValueCatalogs.MAPS_CONTACT_TYPES.get(contactType);
436 			Node contact = contacts.addNode(name);
437 			contact.addMixin(contactType);
438 
439 			if (linkedOrg == null)
440 				contact.setProperty(PEOPLE_CONTACT_VALUE, value);
441 			else {
442 				Node orgPrimaryContact = getPrimaryContact(linkedOrg, contactType);
443 				if (orgPrimaryContact != null)
444 					copyContactValues(orgPrimaryContact, contact);
445 				markAsPro(contact, linkedOrg);
446 			}
447 
448 			if (isPrimary)
449 				markAsPrimary(resourcesService, peopleService, contactable, contact);
450 			setContactCategory(contact, contactType, title);
451 			if (notEmpty(description))
452 				contact.setProperty(Property.JCR_DESCRIPTION, description);
453 			return contact;
454 		} catch (RepositoryException re) {
455 			throw new PeopleException(
456 					"Cannot create " + contactType + " contact with value [" + value + "] on " + contactable, re);
457 		}
458 	}
459 
460 	private static void copyContactValues(Node srcContact, Node targetContact) throws RepositoryException {
461 		targetContact.setProperty(PEOPLE_CONTACT_VALUE, srcContact.getProperty(PEOPLE_CONTACT_VALUE).getString());
462 
463 		if (targetContact.isNodeType(PeopleTypes.PEOPLE_POSTAL_ADDRESS)) {
464 			for (String prop : PEOPLE_POSTAL_ADDRESS_PROPS) {
465 				String value = ConnectJcrUtils.get(srcContact, prop);
466 				if (notEmpty(value))
467 					targetContact.setProperty(prop, targetContact);
468 			}
469 		}
470 	}
471 
472 	/**
473 	 * Create a mail address node. Corresponding nodes are not saved
474 	 * 
475 	 * @param resourcesService
476 	 * @param peopleService
477 	 * @param contactable
478 	 *            the parent item on which we want to add the email
479 	 * @param emailAddress
480 	 *            the value
481 	 * @param title
482 	 *            the generic label for this email
483 	 * @param description
484 	 *            an optional description
485 	 */
486 	public static Node createEmail(ResourcesService resourcesService, PeopleService peopleService, Node contactable,
487 			String emailAddress, boolean isPrimary, String title, String description) {
488 		return createContact(resourcesService, peopleService, contactable, PeopleTypes.PEOPLE_MAIL, emailAddress,
489 				isPrimary, title, description);
490 	}
491 
492 	/**
493 	 * Create a web site URL node. Corresponding nodes are not saved
494 	 * 
495 	 * @param parentNode
496 	 *            the parent item on which we want to add a contact
497 	 * @param urlString
498 	 * @param isPrimary
499 	 * @param title
500 	 *            an optional label
501 	 * 
502 	 * @return
503 	 */
504 	public static Node createWebsite(ResourcesService resourcesService, PeopleService peopleService, Node parentNode,
505 			String urlString, boolean isPrimary, String title) {
506 		return createContact(resourcesService, peopleService, parentNode, PeopleTypes.PEOPLE_URL, urlString, isPrimary,
507 				title, null);
508 	}
509 
510 	public static Node createSocialMedia(ResourcesService resourcesService, PeopleService peopleService,
511 			Node contactable, String urlString, boolean isPrimary, String title, String description) {
512 		return createContact(resourcesService, peopleService, contactable, PeopleTypes.PEOPLE_SOCIAL_MEDIA, urlString,
513 				isPrimary, title, description);
514 	}
515 
516 	public static Node createImpp(ResourcesService resourcesService, PeopleService peopleService, Node contactable,
517 			String urlString, boolean isPrimary, String title, String description) {
518 		return createContact(resourcesService, peopleService, contactable, PeopleTypes.PEOPLE_IMPP, urlString,
519 				isPrimary, title, description);
520 	}
521 
522 	/**
523 	 * Create a landline phone number node for the current entity
524 	 * 
525 	 * @param contactable
526 	 * @param phoneNumber
527 	 * @param isPrimary
528 	 * @param title
529 	 * @param description
530 	 *            an optional label
531 	 * 
532 	 * @return
533 	 */
534 	public static Node createPhone(ResourcesService resourcesService, PeopleService peopleService, Node contactable,
535 			String phoneNumber, boolean isPrimary, String title, String description) {
536 		return createContact(resourcesService, peopleService, contactable, PeopleTypes.PEOPLE_TELEPHONE_NUMBER,
537 				phoneNumber, isPrimary, title, description);
538 	}
539 
540 	/**
541 	 * Create an address for the current entity
542 	 * 
543 	 * @param contactable
544 	 * @param street1
545 	 * @param street2
546 	 * @param zipCode
547 	 * @param city
548 	 * @param state
549 	 * @param country
550 	 * @param isPrimary
551 	 * @param title
552 	 * @param description
553 	 *            an optional label
554 	 * 
555 	 * @return
556 	 */
557 	public static Node createAddress(ResourcesService resourcesService, PeopleService peopleService, Node contactable,
558 			String street1, String street2, String zipCode, String city, String state, String country,
559 			boolean isPrimary, String title, String description) {
560 		return createAddress(resourcesService, peopleService, contactable, street1, street2, zipCode, city, state,
561 				country, null, isPrimary, title, description);
562 	}
563 
564 	public static Node createAddress(ResourcesService resourcesService, PeopleService peopleService, Node contactable,
565 			Dictionary<String, Object> p, boolean isPrimary, String title, String description) {
566 		// TODO deal with multiline street
567 		return createAddress(resourcesService, peopleService, contactable, get(p, LdapAttrs.street), null,
568 				get(p, LdapAttrs.postalCode), get(p, LdapAttrs.l), get(p, LdapAttrs.st), get(p, LdapAttrs.c), null,
569 				isPrimary, title, description);
570 	}
571 
572 	/**
573 	 * Create an address with a geopoint for the current entity
574 	 * 
575 	 * @param contactable
576 	 * @param isPrimary
577 	 * @param title
578 	 * @param description
579 	 *            an optional label
580 	 * @param street1
581 	 * @param street2
582 	 * @param zipCode
583 	 * @param city
584 	 * @param state
585 	 * @param country
586 	 * @param nature
587 	 *            optional: if parent node type is a person, then precise private or
588 	 *            pro
589 	 * 
590 	 * @return
591 	 */
592 	public static Node createWorkAddress(ResourcesService resourcesService, PeopleService peopleService,
593 			Node contactable, boolean isPrimary, Node linkedOrg, String title, String description) {
594 		// try {
595 		Node address = createContact(resourcesService, peopleService, contactable, PeopleTypes.PEOPLE_POSTAL_ADDRESS,
596 				"", false, linkedOrg, title, description);
597 		// address.addMixin(PeopleTypes.PEOPLE_CONTACT_REF);
598 		// // set reference field
599 		// if (referencedOrg != null)
600 		// address.setProperty(PEOPLE_REF_UID,
601 		// referencedOrg.getProperty(ConnectNames.CONNECT_UID).getString());
602 		//
603 		// if (primary)
604 		// markAsPrimary(resourcesService, peopleService, parentNode, address);
605 		//
606 		return address;
607 		// } catch (RepositoryException re) {
608 		// throw new PeopleException("Unable to add a new address node", re);
609 		// }
610 	}
611 
612 	/**
613 	 * Create an address with a geopoint for the current entity
614 	 * 
615 	 * @param contactable
616 	 * @param street1
617 	 * @param street2
618 	 * @param zipCode
619 	 * @param city
620 	 * @param state
621 	 * @param country
622 	 * @param geopoint
623 	 * @param isPrimary
624 	 * @param title
625 	 * @param description
626 	 *            an optional label
627 	 * 
628 	 * @return
629 	 */
630 	public static Node createAddress(ResourcesService resourcesService, PeopleService peopleService, Node contactable,
631 			String street1, String street2, String zipCode, String city, String state, String country, String geopoint,
632 			boolean isPrimary, String title, String description) {
633 		try {
634 			// Postpone primary flag management
635 			Node address = createContact(resourcesService, peopleService, contactable,
636 					PeopleTypes.PEOPLE_POSTAL_ADDRESS, "", false, title, description);
637 			// set address fields
638 			if (notEmpty(street1))
639 				address.setProperty(PEOPLE_STREET, street1);
640 
641 			if (notEmpty(street2))
642 				address.setProperty(PEOPLE_STREET_COMPLEMENT, street2);
643 
644 			if (notEmpty(zipCode))
645 				address.setProperty(PEOPLE_ZIP_CODE, zipCode);
646 
647 			if (notEmpty(city))
648 				address.setProperty(PEOPLE_CITY, city);
649 
650 			if (notEmpty(state))
651 				address.setProperty(PEOPLE_STATE, state);
652 
653 			if (notEmpty(country))
654 				address.setProperty(PEOPLE_COUNTRY, country);
655 
656 			if (notEmpty(geopoint))
657 				address.setProperty(PEOPLE_GEOPOINT, geopoint);
658 
659 			updateDisplayAddress(resourcesService, address);
660 
661 			// update primary flag after contact creation
662 			if (isPrimary)
663 				markAsPrimary(resourcesService, peopleService, contactable, address);
664 			return address;
665 		} catch (RepositoryException re) {
666 			throw new PeopleException("Unable to add a new address node", re);
667 		}
668 	}
669 
670 	public static void updateDisplayAddress(ResourcesService resourcesService, Node contactNode) {
671 		try {
672 			String res = getPostalAddress(resourcesService, contactNode);
673 			if (notEmpty(res))
674 				contactNode.setProperty(PeopleNames.PEOPLE_CONTACT_VALUE, res);
675 		} catch (RepositoryException e) {
676 			throw new PeopleException("Cannot update human readable postal address for " + contactNode, e);
677 		}
678 	}
679 
680 	public static String getPostalAddress(ResourcesService resourcesService, Node contactNode) {
681 		StringBuilder displayAddress = new StringBuilder();
682 		List<String> pieces = new ArrayList<String>();
683 
684 		pieces.add(ConnectJcrUtils.get(contactNode, PeopleNames.PEOPLE_STREET));
685 		pieces.add(ConnectJcrUtils.get(contactNode, PeopleNames.PEOPLE_STREET_COMPLEMENT));
686 
687 		String zip = ConnectJcrUtils.get(contactNode, PeopleNames.PEOPLE_ZIP_CODE);
688 		String city = ConnectJcrUtils.get(contactNode, PeopleNames.PEOPLE_CITY);
689 		// TODO use the inverse order when displaying addresses for UK like
690 		// countries
691 		String zc = ConnectJcrUtils.concatIfNotEmpty(zip, city, " ");
692 		pieces.add(zc);
693 		String countryStr = ConnectJcrUtils.get(contactNode, PeopleNames.PEOPLE_STATE);
694 		if (notEmpty(countryStr) && resourcesService != null)
695 			countryStr = resourcesService.getEncodedTagValue(ConnectJcrUtils.getSession(contactNode),
696 					ConnectConstants.RESOURCE_COUNTRY, countryStr);
697 		pieces.add(countryStr);
698 		pieces.add(ConnectJcrUtils.get(contactNode, PeopleNames.PEOPLE_COUNTRY));
699 
700 		for (String piece : pieces) {
701 			if (notEmpty(piece))
702 				displayAddress.append(piece).append(", ");
703 		}
704 		String res = displayAddress.toString();
705 		if (notEmpty(res))
706 			res = res.substring(0, res.lastIndexOf(", "));
707 		return res;
708 	}
709 
710 	/**
711 	 * Set the default picture for the current entity, overwrite without asking but
712 	 * do not save and check in corresponding node
713 	 */
714 	public static void setEntityPicture(Node entity, InputStream picture, String fileName) throws RepositoryException {
715 		Node picNode = JcrUtils.mkdirs(entity, ConnectNames.CONNECT_PHOTO, NodeType.NT_FILE);
716 		Node contentNode;
717 		if (picNode.hasNode(Node.JCR_CONTENT))
718 			contentNode = picNode.getNode(Node.JCR_CONTENT);
719 		else
720 			contentNode = picNode.addNode(Node.JCR_CONTENT, NodeType.NT_RESOURCE);
721 		Binary binary = null;
722 		binary = entity.getSession().getValueFactory().createBinary(picture);
723 		contentNode.setProperty(Property.JCR_DATA, binary);
724 		contentNode.setProperty(Property.JCR_MIMETYPE,
725 				fileName.substring(fileName.lastIndexOf("."), fileName.length()));
726 	}
727 
728 	/** Calls JcrUtils */
729 	public static String replaceInvalidChars(String string) {
730 		string = JcrUtils.replaceInvalidChars(string);
731 		return string.replace(' ', '_');
732 	}
733 
734 	public static Node createImportTmpParent(Session session, AppService appService) throws RepositoryException {
735 		Node peopleDraftParent = appService.getDraftParent(session);
736 		String relPath = "imports/"
737 				+ appService.getDefaultRelPath(session, ConnectTypes.CONNECT_ENTITY, session.getUserID());
738 		Node parent = JcrUtils.mkdirs(peopleDraftParent, ConnectJcrUtils.parentRelPath(relPath));
739 		return parent.addNode(session.getUserID());
740 	}
741 
742 	public static void syncPerson(Dictionary<String, Object> user, Node person) throws RepositoryException {
743 		sync(user, LdapAttrs.mail, person, PeopleNames.PEOPLE_PRIMARY_EMAIL);
744 		sync(user, LdapAttrs.givenName, person, PeopleNames.PEOPLE_FIRST_NAME);
745 		sync(user, LdapAttrs.sn, person, PeopleNames.PEOPLE_LAST_NAME);
746 		sync(user, LdapAttrs.displayName, person, PeopleNames.PEOPLE_DISPLAY_NAME);
747 		Object dateOfBirth = user.get(LdapAttrs.dateOfBirth.name());
748 		if (dateOfBirth != null) {
749 			Calendar calendar = NamingUtils.ldapDateToCalendar(dateOfBirth.toString());
750 			person.setProperty(PeopleNames.PEOPLE_BIRTH_DATE, calendar);
751 		}
752 	}
753 
754 	/** From {@link User} to a person or contact */
755 	private static void sync(Dictionary<String, Object> properties, LdapAttrs key, Node node, String property)
756 			throws RepositoryException {
757 		Object value = properties.get(key.name());
758 		if (value == null)
759 			return;
760 		node.setProperty(property, value.toString());
761 	}
762 
763 	/** Safe get */
764 	private static String get(Dictionary<String, Object> properties, LdapAttrs key) {
765 		Object value = properties.get(key.name());
766 		if (value == null)
767 			return null;
768 		return value.toString();
769 	}
770 }