View Javadoc
1   package org.argeo.connect.resources.core;
2   
3   import static org.argeo.connect.util.ConnectUtils.notEmpty;
4   
5   import java.util.ArrayList;
6   import java.util.List;
7   import java.util.UUID;
8   
9   import javax.jcr.Node;
10  import javax.jcr.NodeIterator;
11  import javax.jcr.Property;
12  import javax.jcr.PropertyType;
13  import javax.jcr.RepositoryException;
14  import javax.jcr.Session;
15  import javax.jcr.Value;
16  import javax.jcr.nodetype.NodeType;
17  import javax.jcr.query.Query;
18  import javax.jcr.query.QueryManager;
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.cms.auth.CurrentUser;
24  import org.argeo.connect.ConnectConstants;
25  import org.argeo.connect.ConnectException;
26  import org.argeo.connect.ConnectNames;
27  import org.argeo.connect.ConnectTypes;
28  import org.argeo.connect.core.AbstractAppService;
29  import org.argeo.connect.resources.ResourcesConstants;
30  import org.argeo.connect.resources.ResourcesNames;
31  import org.argeo.connect.resources.ResourcesRole;
32  import org.argeo.connect.resources.ResourcesService;
33  import org.argeo.connect.resources.ResourcesTypes;
34  import org.argeo.connect.util.ConnectJcrUtils;
35  import org.argeo.connect.util.ConnectUtils;
36  import org.argeo.connect.util.XPathUtils;
37  import org.argeo.jcr.JcrUtils;
38  
39  /** Concrete access to people {@link ResourcesService} */
40  public class ResourcesServiceImpl extends AbstractAppService implements ResourcesService {
41  	private final static Log log = LogFactory.getLog(ResourcesServiceImpl.class);
42  
43  	/* API METHODS */
44  	@Override
45  	public Node publishEntity(Node parent, String nodeType, Node srcNode, boolean removeSrcNode)
46  			throws RepositoryException {
47  		// TODO Auto-generated method stub
48  		return null;
49  	}
50  
51  	@Override
52  	public String getAppBaseName() {
53  		return ResourcesNames.RESOURCES_BASE_NAME;
54  	}
55  
56  	@Override
57  	public String getDefaultRelPath(Node entity) throws RepositoryException {
58  		// TODO Auto-generated method stub
59  		return null;
60  	}
61  
62  	@Override
63  	public String getDefaultRelPath(Session session, String nodeType, String id) {
64  		if (ResourcesTypes.RESOURCES_TAG.equals(nodeType) | ResourcesTypes.RESOURCES_ENCODED_TAG.equals(nodeType)) {
65  			// remove trailing and starting space
66  			id = id.trim();
67  			String cleanedTag = JcrUtils.replaceInvalidChars(id).trim();
68  			// corner case when second character is a space
69  			if (cleanedTag.charAt(1) == ' ')
70  				cleanedTag = cleanedTag.charAt(0) + ' ' + cleanedTag.substring(2);
71  			String relPath = JcrUtils.firstCharsToPath(cleanedTag, 2);
72  			return relPath + "/" + cleanedTag;
73  		} else if (ResourcesTypes.RESOURCES_NODE_TEMPLATE.equals(nodeType))
74  			return ResourcesNames.RESOURCES_TEMPLATES + "/" + id;
75  		else if (ResourcesConstants.RESOURCE_TYPE_ID_TEMPLATE.equals(nodeType)) {
76  			log.warn("Deprecated method argument : " + nodeType);
77  			return ResourcesNames.RESOURCES_TEMPLATES + "/" + id;
78  		} else if (ResourcesTypes.RESOURCES_TAG_PARENT.equals(nodeType))
79  			return ResourcesNames.RESOURCES_TAG_LIKE + "/" + id;
80  		else if (ResourcesConstants.RESOURCE_TYPE_ID_TAG_LIKE.equals(nodeType)) {
81  			log.warn("Deprecated method argument : " + nodeType);
82  			return ResourcesNames.RESOURCES_TAG_LIKE + "/" + id;
83  		} else
84  			return null;
85  	}
86  
87  	@Override
88  	public boolean isKnownType(Node entity) {
89  		if (ConnectJcrUtils.isNodeType(entity, ResourcesTypes.RESOURCES_TAG_PARENT)
90  				|| ConnectJcrUtils.isNodeType(entity, ResourcesTypes.RESOURCES_NODE_TEMPLATE)
91  				|| ConnectJcrUtils.isNodeType(entity, ResourcesTypes.RESOURCES_ENCODED_TAG)
92  				|| ConnectJcrUtils.isNodeType(entity, ResourcesTypes.RESOURCES_TAG))
93  			return true;
94  		else
95  			return false;
96  	}
97  
98  	@Override
99  	public boolean isKnownType(String nodeType) {
100 		if (ResourcesTypes.RESOURCES_TAG_PARENT.equals(nodeType)
101 				|| ResourcesTypes.RESOURCES_NODE_TEMPLATE.equals(nodeType)
102 				|| ResourcesTypes.RESOURCES_ENCODED_TAG.equals(nodeType)
103 				|| ResourcesTypes.RESOURCES_TAG.equals(nodeType))
104 			return true;
105 		else
106 			return false;
107 	}
108 
109 	/* RESOURCE SERVICE SPECIFIC METHODS */
110 
111 	@Override
112 	public String getItemDefaultEnLabel(String itemId) {
113 		return itemId;
114 	}
115 
116 	@Override
117 	public String getItemLabel(String itemName, String langIso) {
118 		log.warn("Item label retrieval is not yet internationnalized. " + "Returning english default label for "
119 				+ itemName + " instead.");
120 		return getItemDefaultEnLabel(itemName);
121 	}
122 
123 	/* TEMPLATES AND CORRESPONDING CATALOGUES */
124 	@Override
125 	public List<String> getTemplateCatalogue(Session session, String templateId, String propertyName, String filter) {
126 		Node node = getNodeTemplate(session, templateId);
127 		if (node != null)
128 			return getTemplateCatalogue(node, propertyName, filter);
129 		else
130 			// we have the contract to always return a list, even empty
131 			return new ArrayList<String>();
132 	}
133 
134 	@Override
135 	public List<String> getTemplateCatalogue(Node node, String propertyName, String filter) {
136 		List<String> result = new ArrayList<String>();
137 		try {
138 			if (node.hasProperty(propertyName)) {
139 				Value[] values = node.getProperty(propertyName).getValues();
140 				for (Value value : values) {
141 					String curr = value.getString();
142 					if (ConnectUtils.isEmpty(filter)
143 							|| notEmpty(curr) && curr.toLowerCase().contains(filter.toLowerCase()))
144 						result.add(curr);
145 				}
146 			}
147 		} catch (RepositoryException re) {
148 			throw new ConnectException("Unable to get " + propertyName + " values for template " + node, re);
149 		}
150 		return result;
151 	}
152 
153 	@Override
154 	public Node getNodeTemplate(Session session, String templateId) {
155 		String path = "/" + getAppBaseName() + "/"
156 				+ getDefaultRelPath(null, ResourcesTypes.RESOURCES_NODE_TEMPLATE, templateId);
157 		try {
158 			if (session.nodeExists(path)) {
159 				return session.getNode(path);
160 			}
161 		} catch (RepositoryException e) {
162 			throw new ConnectException(
163 					"Unable to retrieve template instance " + "at path " + path + " for templateId " + templateId, e);
164 		}
165 		return null;
166 	}
167 
168 	@Override
169 	public Node createTemplateForType(Session session, String nodeType, String templateId) {
170 		String currId = templateId == null ? nodeType : templateId;
171 		Node node = getNodeTemplate(session, currId);
172 		if (node != null)
173 			return node;
174 		else {
175 			String path = "/" + getAppBaseName() + "/"
176 					+ getDefaultRelPath(null, ResourcesTypes.RESOURCES_NODE_TEMPLATE, currId);
177 			String parPath = JcrUtils.parentPath(path);
178 			try {
179 				if (!session.nodeExists(parPath))
180 					throw new ConnectException(
181 							"Default tree structure " + "for template resources must have been created. "
182 									+ "Please fix this before trying to create template " + currId);
183 				Node parent = session.getNode(parPath);
184 				Node template = parent.addNode(currId, ResourcesTypes.RESOURCES_NODE_TEMPLATE);
185 				template.setProperty(ResourcesNames.RESOURCES_TEMPLATE_ID, currId);
186 				return template;
187 			} catch (RepositoryException e) {
188 				throw new ConnectException("Unable to create new temaple " + currId + " at path " + path, e);
189 			}
190 		}
191 	}
192 
193 	@Override
194 	public NodeIterator getCatalogueValueInstances(Session session, String entityType, String propName, String value) {
195 		try {
196 			QueryManager qm = session.getWorkspace().getQueryManager();
197 			Query query = qm.createQuery(
198 					"select * from [" + entityType + "] as nodes where [" + propName + "]='" + value + "'" + " ",
199 					Query.JCR_SQL2);
200 			return query.execute().getNodes();
201 		} catch (RepositoryException ee) {
202 			throw new ConnectException("Unable to retrieve catalogue value instances for type " + entityType
203 					+ " and property " + propName + " for value " + value, ee);
204 		}
205 	}
206 
207 	@Override
208 	public void updateCatalogueValue(Node templateNode, String taggableType, String propertyName, String oldValue,
209 			String newValue) {
210 		try {
211 
212 			if (ConnectUtils.isEmpty(oldValue))
213 				throw new ConnectException("Old value cannot be empty");
214 
215 			Value[] values = templateNode.getProperty(propertyName).getValues();
216 			List<String> newValues = new ArrayList<String>();
217 
218 			for (Value value : values) {
219 				String currValStr = value.getString();
220 				if (oldValue.equals(currValStr)) {
221 					if (notEmpty(newValue))
222 						newValues.add(newValue);
223 				} else
224 					newValues.add(currValStr);
225 			}
226 			templateNode.setProperty(propertyName, newValues.toArray(new String[0]));
227 
228 			// TODO use a transaction
229 			// Retrieve all node that reference this tag and update them
230 			NodeIterator nit = getCatalogueValueInstances(ConnectJcrUtils.getSession(templateNode), taggableType,
231 					propertyName, oldValue);
232 			while (nit.hasNext())
233 				updateOneTag(nit.nextNode(), propertyName, oldValue, newValue);
234 		} catch (RepositoryException e) {
235 			throw new ConnectException(
236 					"Unable to update " + templateNode + " with values on " + taggableType + " for property "
237 							+ propertyName + " with old value '" + oldValue + "' and new value '" + newValue + "'.",
238 					e);
239 		}
240 	}
241 
242 	/* TAG LIKE INSTANCES MANAGEMENT */
243 	@Override
244 	public Node createTagLikeResourceParent(Session session, String tagId, String tagInstanceType, String codePropName,
245 			String taggableParentPath, String taggableNodeType, String taggablePropName) {
246 		List<String> names = new ArrayList<String>();
247 		names.add(taggablePropName);
248 		return createTagLikeResourceParent(session, tagId, tagInstanceType, codePropName, taggableParentPath,
249 				taggableNodeType, names);
250 	}
251 
252 	public Node createTagLikeResourceParent(Session session, String tagId, String tagInstanceType, String codePropName,
253 			String taggableParentPath, String taggableNodeType, List<String> taggablePropNames) {
254 		String currId = tagId == null ? tagInstanceType : tagId;
255 		Node node = getTagLikeResourceParent(session, currId);
256 		if (node != null)
257 			return node;
258 		else {
259 			String path = "/" + getAppBaseName() + "/"
260 					+ getDefaultRelPath(null, ResourcesTypes.RESOURCES_TAG_PARENT, currId);
261 			String parPath = JcrUtils.parentPath(path);
262 			try {
263 				if (!session.nodeExists(parPath))
264 					throw new ConnectException("Default tree structure "
265 							+ "for tag like resources must have been created. "
266 							+ "Please fix this before trying to create " + "tag like resource parent " + currId);
267 				Node parent = session.getNode(parPath);
268 				Node tagLikeParent = parent.addNode(currId, ResourcesTypes.RESOURCES_TAG_PARENT);
269 				tagLikeParent.setProperty(ResourcesNames.RESOURCES_TAG_ID, currId);
270 				tagLikeParent.setProperty(ResourcesNames.RESOURCES_TAG_INSTANCE_TYPE, tagInstanceType);
271 				// If this property is not set, the key property of the tag
272 				// instance is the JCR_TITLE Property
273 				if (notEmpty(codePropName))
274 					tagLikeParent.setProperty(ResourcesNames.RESOURCES_TAG_CODE_PROP_NAME, codePropName);
275 				tagLikeParent.setProperty(ResourcesNames.RESOURCES_TAGGABLE_PARENT_PATH, taggableParentPath);
276 				tagLikeParent.setProperty(ResourcesNames.RESOURCES_TAGGABLE_NODE_TYPE, taggableNodeType);
277 				tagLikeParent.setProperty(ResourcesNames.RESOURCES_TAGGABLE_PROP_NAME,
278 						taggablePropNames.toArray(new String[0]));
279 				return tagLikeParent;
280 			} catch (RepositoryException e) {
281 				throw new ConnectException("Unable to create new temaple " + currId + " at path " + path, e);
282 			}
283 		}
284 	}
285 
286 	@Override
287 	public Node getTagLikeResourceParent(Session session, String tagId) {
288 		String path = "/" + getAppBaseName() + "/"
289 				+ getDefaultRelPath(null, ResourcesTypes.RESOURCES_TAG_PARENT, tagId);
290 		try {
291 			if (session.nodeExists(path)) {
292 				return session.getNode(path);
293 			}
294 		} catch (RepositoryException e) {
295 			throw new ConnectException("Unable to retrieve tag like parent at path " + path + " for tagId " + tagId, e);
296 		}
297 		return null;
298 	}
299 
300 	@Override
301 	public Node registerTag(Session session, String tagId, String tagValue) throws RepositoryException {
302 		Node tagInstance = getRegisteredTag(session, tagId, tagValue);
303 		if (tagInstance != null) {
304 			// Tag already exists, we do nothing.
305 			if (log.isTraceEnabled()) {
306 				String registeredKey = ConnectJcrUtils.get(tagInstance,
307 						getTagKeyPropName(getTagLikeResourceParent(session, tagId)));
308 				log.debug("Tag [" + tagId + "] with key " + tagValue + " already exists (registered key is ["
309 						+ registeredKey + "]), nothing has been done.");
310 			}
311 			return tagInstance;
312 		} else {
313 			Node newTag = createTagInstanceInternal(getExistingTagLikeParent(session, tagId), tagValue);
314 			return newTag;
315 		}
316 	}
317 
318 	@Override
319 	public Node registerTag(Session session, String tagId, String tagCode, String tagValue) throws RepositoryException {
320 		// Check if such a tag already exists
321 		Node tagInstance = getRegisteredTag(session, tagId, tagCode);
322 		if (tagInstance != null) {
323 			// Tag already exists, we do nothing.
324 			if (log.isTraceEnabled()) {
325 				String registeredKey = ConnectJcrUtils.get(tagInstance,
326 						getTagKeyPropName(getTagLikeResourceParent(session, tagId)));
327 				log.debug("Tag [" + tagId + "] with key " + tagCode + " already exists (registered key is ["
328 						+ registeredKey + "]), nothing has been done.");
329 			}
330 			return tagInstance;
331 		} else {
332 			Node newTag = createTagInstanceInternal(getExistingTagLikeParent(session, tagId), tagCode);
333 			// remove trailing and starting space
334 			tagValue = tagValue.trim();
335 			ConnectJcrUtils.setJcrProperty(newTag, Property.JCR_TITLE, PropertyType.STRING, tagValue);
336 			return newTag;
337 		}
338 	}
339 
340 	@Override
341 	public Node getRegisteredTag(Session session, String tagId, String instanceKey) {
342 		return getRegisteredTag(getExistingTagLikeParent(session, tagId), instanceKey);
343 	}
344 
345 	@Override
346 	public Node getRegisteredTag(Node tagParent, String instanceKey) {
347 		try {
348 			String relPath = getDefaultRelPath(null, ResourcesTypes.RESOURCES_TAG, instanceKey);
349 			if (tagParent.hasNode(relPath)) {
350 				Node existing = tagParent.getNode(relPath);
351 				String existingValue = ConnectJcrUtils.get(existing, getTagKeyPropName(tagParent));
352 				if (instanceKey.equalsIgnoreCase(existingValue)) {
353 					return existing;
354 				}
355 			}
356 			return null;
357 		} catch (RepositoryException e) {
358 			throw new ConnectException("Unable to retrieve existing tag " + tagParent + " for key " + instanceKey, e);
359 		}
360 	}
361 
362 	@Override
363 	public String getEncodedTagValue(Session session, String tagId, String code) {
364 		try {
365 			Node tag = getRegisteredTag(session, tagId, code);
366 			if (tag != null && tag.hasProperty(Property.JCR_TITLE))
367 				return tag.getProperty(Property.JCR_TITLE).getString();
368 		} catch (RepositoryException e) {
369 			throw new ConnectException("Unable to retrieve tag " + tagId + " value with key " + code, e);
370 		}
371 		return code;
372 	}
373 
374 	@Override
375 	public String getEncodedTagCodeFromValue(Session session, String tagId, String value) {
376 		try {
377 			Node tagParent = getExistingTagLikeParent(session, tagId);
378 			String tagNodeType = tagParent.getProperty(ResourcesNames.RESOURCES_TAG_INSTANCE_TYPE).getString();
379 			String tagCodePropName = tagParent.getProperty(ResourcesNames.RESOURCES_TAG_CODE_PROP_NAME).getString();
380 			// XPath
381 			StringBuilder builder = new StringBuilder();
382 			builder.append(XPathUtils.descendantFrom(tagParent.getPath()));
383 			builder.append("//element(*, ").append(tagNodeType).append(")");
384 			builder.append("[");
385 			builder.append(XPathUtils.getPropertyEquals(Property.JCR_TITLE, value));
386 			builder.append("]");
387 
388 			Query query = XPathUtils.createQuery(session, builder.toString());
389 			QueryResult queryResult = query.execute();
390 			NodeIterator ni = queryResult.getNodes();
391 			if (ni.hasNext())
392 				return ni.nextNode().getProperty(tagCodePropName).getString();
393 			else {
394 				return null;
395 			}
396 		} catch (RepositoryException e) {
397 			throw new ConnectException("Unable to get code for encodedTag " + tagId + " with value " + value, e);
398 		}
399 	}
400 
401 	@Override
402 	public String getEncodedTagValuesAsString(String tagId, Node node, String propertyName, String separator) {
403 		try {
404 			Session session = node.getSession();
405 
406 			if (!node.hasProperty(propertyName))
407 				return "";
408 			else {
409 				Value[] codes = node.getProperty(propertyName).getValues();
410 				StringBuilder builder = new StringBuilder();
411 				for (Value val : codes) {
412 					String currCode = val.getString();
413 					if (notEmpty(currCode)) {
414 						builder.append(getEncodedTagValue(session, tagId, currCode)).append(separator);
415 					}
416 				}
417 				if (builder.lastIndexOf(separator) >= 0)
418 					return builder.substring(0, builder.length() - separator.length());
419 				else
420 					return builder.toString();
421 			}
422 		} catch (RepositoryException e) {
423 			throw new ConnectException(
424 					"Cannot get values of " + tagId + " for property " + propertyName + " of " + node, e);
425 		}
426 	}
427 
428 	@Override
429 	public NodeIterator getTaggedEntities(Session session, String tagId, String key) {
430 		Node tagParent = getExistingTagLikeParent(session, tagId);
431 		return getTaggedEntities(tagParent, key);
432 	}
433 
434 	@Override
435 	public NodeIterator getTaggedEntities(Node tagParent, String key) {
436 		try {
437 			String nodeType = tagParent.getProperty(ResourcesNames.RESOURCES_TAGGABLE_NODE_TYPE).getString();
438 			String parentPath = tagParent.getProperty(ResourcesNames.RESOURCES_TAGGABLE_PARENT_PATH).getString();
439 			List<String> propNames = ConnectJcrUtils.getMultiAsList(tagParent,
440 					ResourcesNames.RESOURCES_TAGGABLE_PROP_NAME);
441 
442 			// Build xpath for property names;
443 			StringBuilder builder = new StringBuilder();
444 			// String cleanedKey = key.replaceAll("(?:')", "''");
445 			for (String propName : propNames) {
446 				builder.append(XPathUtils.getPropertyEquals(propName, key));
447 				builder.append(" OR ");
448 			}
449 			String condition = builder.toString();
450 			// if (condition.endsWith(" OR "))
451 			// force error if no property is defined for this tag
452 			condition = condition.substring(0, condition.length() - 4);
453 
454 			String xpathQueryStr = XPathUtils.descendantFrom(parentPath) + "//element(*, " + nodeType + ")";
455 
456 			xpathQueryStr += "[" + condition + "]";
457 			QueryManager queryManager = tagParent.getSession().getWorkspace().getQueryManager();
458 			Query query = queryManager.createQuery(xpathQueryStr, ConnectConstants.QUERY_XPATH);
459 			NodeIterator nit = query.execute().getNodes();
460 			return nit;
461 		} catch (RepositoryException ee) {
462 			throw new ConnectException("Unable to retrieve tagged entities for key " + key + " on parent " + tagParent,
463 					ee);
464 		}
465 	}
466 
467 	@Override
468 	public void refreshKnownTags(Session session, String tagId) {
469 		refreshKnownTags(getExistingTagLikeParent(session, tagId));
470 	}
471 
472 	@Override
473 	public void refreshKnownTags(Node tagParent) {
474 		try {
475 			// Initialisation
476 			List<String> newExistingValues = new ArrayList<String>();
477 			List<String> registeredTags = new ArrayList<String>();
478 			Session session = tagParent.getSession();
479 			if (log.isDebugEnabled())
480 				log.debug("Starting known tag refresh for " + tagParent);
481 			String keyPropName = getTagKeyPropName(tagParent);
482 			String taggableNodeType = tagParent.getProperty(ResourcesNames.RESOURCES_TAGGABLE_NODE_TYPE).getString();
483 			String taggableParentPath = tagParent.getProperty(ResourcesNames.RESOURCES_TAGGABLE_PARENT_PATH)
484 					.getString();
485 			String codeProp = ConnectJcrUtils.get(tagParent, ResourcesNames.RESOURCES_TAG_CODE);
486 			List<String> propNames = ConnectJcrUtils.getMultiAsList(tagParent,
487 					ResourcesNames.RESOURCES_TAGGABLE_PROP_NAME);
488 
489 			if (log.isTraceEnabled())
490 				log.trace("Getting already registered tags");
491 			NodeIterator nit = getRegisteredTags(tagParent, null);
492 			while (nit.hasNext()) {
493 				Node currNode = nit.nextNode();
494 				String currKey = ConnectJcrUtils.get(currNode, keyPropName);
495 				if (notEmpty(currKey) && !registeredTags.contains(currKey))
496 					registeredTags.add(currKey);
497 			}
498 			if (log.isTraceEnabled())
499 				log.trace("Found already " + registeredTags.size() + " registered tags");
500 
501 			// Look for not yet registered tags
502 			// Query query = session
503 			// .getWorkspace()
504 			// .getQueryManager()
505 			// .createQuery(
506 			// "select * from [" + taggableNodeType
507 			// + "] as instances where ISDESCENDANTNODE('"
508 			// + taggableParentPath + "') ",
509 			// Query.JCR_SQL2);
510 
511 			String xpathQueryStr = XPathUtils.descendantFrom(taggableParentPath) + "//element(*, " + taggableNodeType
512 					+ ")";
513 			QueryManager queryManager = session.getWorkspace().getQueryManager();
514 			Query query = queryManager.createQuery(xpathQueryStr, ConnectConstants.QUERY_XPATH);
515 			nit = query.execute().getNodes();
516 			if (log.isDebugEnabled())
517 				log.debug("Searching new tags on " + nit.getSize() + " elements of type " + taggableNodeType);
518 			while (nit.hasNext()) {
519 				Node currNode = nit.nextNode();
520 				for (String propName : propNames) {
521 					if (currNode.hasProperty(propName)) {
522 						Value[] tags = currNode.getProperty(propName).getValues();
523 						for (Value tagV : tags) {
524 							String currTag = tagV.getString().trim();
525 
526 							if (notEmpty(currTag) && !registeredTags.contains(currTag)) {
527 								if (currTag.length() < 2) {
528 									log.warn("Unable to cache tag [" + currTag + "] for "
529 											+ ConnectJcrUtils.get(currNode, Property.JCR_TITLE) + " - " + currNode);
530 								} else if (!newExistingValues.contains(currTag))
531 									newExistingValues.add(currTag);
532 							}
533 						}
534 					}
535 				}
536 			}
537 
538 			if (log.isTraceEnabled())
539 				log.trace("Start processing the " + newExistingValues.size() + " new used tags found.");
540 
541 			// Add the newly found tags.
542 			if (ConnectUtils.isEmpty(codeProp))
543 				for (String tag : newExistingValues) {
544 					createTagInstanceInternal(tagParent, tag);
545 					session.save();
546 				}
547 			else {
548 				for (String tag : newExistingValues) {
549 					Node curr = createTagInstanceInternal(tagParent, tag);
550 					ConnectJcrUtils.setJcrProperty(curr, Property.JCR_TITLE, PropertyType.STRING, tag);
551 					session.save();
552 				}
553 				log.warn("We refreshed an encoded tag like subtree, for " + tagParent
554 						+ ". All tag values have been set " + "with the same value as the code because we "
555 						+ "have no information on the corresponding label.");
556 			}
557 			if (log.isDebugEnabled())
558 				log.debug("Tag refresh for " + tagParent + " has been done, creating " + newExistingValues.size()
559 						+ " new tag instances.");
560 		} catch (RepositoryException ee) {
561 			throw new ConnectException("Unable to refresh cache of known tags", ee);
562 		}
563 	}
564 
565 	@Override
566 	public boolean updateTag(Node tagInstance, String newValue) throws RepositoryException {
567 		Node tagParent = TagUtils.retrieveTagParentFromTag(tagInstance);
568 		Value[] values = tagParent.getProperty(ResourcesNames.RESOURCES_TAGGABLE_PROP_NAME).getValues();
569 		if (values.length > 1)
570 			// unimplemented multiple value
571 			return false;
572 		String propName = values[0].getString();
573 		String oldValue = tagInstance.getProperty(Property.JCR_TITLE).getString();
574 
575 		// TODO use a transaction
576 		ConnectJcrUtils.checkCOStatusBeforeUpdate(tagInstance);
577 		Session session = tagInstance.getSession();
578 		// Retrieve all node that reference this tag and update them
579 		NodeIterator nit = getTaggedEntities(tagParent, oldValue);
580 		while (nit.hasNext())
581 			updateOneTag(nit.nextNode(), propName, oldValue, newValue);
582 
583 		String newRelPath = "/" + getDefaultRelPath(null, ResourcesTypes.RESOURCES_TAG, newValue);
584 		// Insure the parent node is already existing
585 		// Rather use the parent node than the abs path: we might have not the
586 		// right on the full workspace
587 		Node newIntancePar = JcrUtils.mkdirs(tagParent, JcrUtils.parentPath(newRelPath), NodeType.NT_UNSTRUCTURED);
588 		session.move(tagInstance.getPath(), newIntancePar.getPath() + "/" + JcrUtils.lastPathElement(newRelPath));
589 		tagInstance.setProperty(Property.JCR_TITLE, newValue);
590 		tagInstance.getSession().save();
591 		return true;
592 	}
593 
594 	/**
595 	 * Save the session for each node in order to manage the version. Should be
596 	 * cleaned when we handle transaction.
597 	 * 
598 	 * @param taggable
599 	 * @param tagPropName
600 	 * @param oldValue
601 	 * @param newValue
602 	 */
603 	private void updateOneTag(Node taggable, String tagPropName, String oldValue, String newValue) {
604 		try {
605 			Node versionable = ConnectJcrUtils.getParentVersionableNode(taggable);
606 			if (versionable != null && !ConnectJcrUtils.checkCOStatusBeforeUpdate(versionable))
607 				log.warn(versionable + " was checked-in as we want to update " + tagPropName + " of " + taggable
608 						+ " form " + oldValue + " to " + newValue);
609 			Property property = taggable.getProperty(tagPropName);
610 			if (property.isMultiple()) {
611 				List<String> oldValues = ConnectJcrUtils.getMultiAsList(taggable, tagPropName);
612 				List<String> newValues = new ArrayList<String>();
613 				for (String val : oldValues) {
614 					if (oldValue.equals(val) && newValue != null)
615 						newValues.add(newValue);
616 					else
617 						newValues.add(val);
618 				}
619 				taggable.setProperty(tagPropName, newValues.toArray(new String[0]));
620 			} else
621 				taggable.setProperty(tagPropName, newValue);
622 			taggable.getSession().save();
623 		} catch (RepositoryException ee) {
624 			throw new ConnectException("Unable to update tag like multiple value property " + tagPropName + " removing "
625 					+ oldValue + " and adding " + newValue + " on " + taggable, ee);
626 		}
627 	}
628 
629 	@Override
630 	public void unregisterTag(Session session, String tagId, String tag) {
631 		throw new ConnectException("Unimplemented method " + "ResourceService.unregisterTag("
632 				+ "Session session, String tagId, String tag) ");
633 	}
634 
635 	@Override
636 	public long countMembers(Node tag) {
637 		Node parent = TagUtils.retrieveTagParentFromTag(tag);
638 		String keyPropName = getTagKeyPropName(parent);
639 		NodeIterator nit = getTaggedEntities(parent, ConnectJcrUtils.get(tag, keyPropName));
640 		return nit.getSize();
641 	}
642 
643 	@Override
644 	public List<String> getRegisteredTagValueList(Session session, String tagId, String filter) {
645 		Node tagParent = getTagLikeResourceParent(session, tagId);
646 		List<String> values = new ArrayList<String>();
647 		if (tagParent == null)// workaround
648 			return values;
649 		try {
650 			NodeIterator nit = getRegisteredTags(tagParent, filter);
651 			while (nit.hasNext()) {
652 				Node curr = nit.nextNode();
653 				if (curr.hasProperty(Property.JCR_TITLE))
654 					values.add(curr.getProperty(Property.JCR_TITLE).getString());
655 			}
656 			return values;
657 		} catch (RepositoryException re) {
658 			throw new ConnectException("Unable to get values for tag of ID " + tagId, re);
659 		}
660 	}
661 
662 	// HELPERS FOR TAGS
663 	/** Already existing check must have been done before */
664 	private Node createTagInstanceInternal(Node tagParent, String tagKey) {
665 		try {
666 			// retrieve parameters for this tag
667 			String relPath = getDefaultRelPath(null, ResourcesTypes.RESOURCES_TAG, tagKey);
668 			String instanceType = tagParent.getProperty(ResourcesNames.RESOURCES_TAG_INSTANCE_TYPE).getString();
669 			// create and set props
670 			Node newTag = JcrUtils.mkdirs(tagParent, relPath, instanceType);
671 			newTag.setProperty(getTagKeyPropName(tagParent), tagKey);
672 			if (newTag.isNodeType(ConnectTypes.CONNECT_ENTITY)) {
673 				String uuid = UUID.randomUUID().toString();
674 				newTag.setProperty(ConnectNames.CONNECT_UID, uuid);
675 			}
676 			return newTag;
677 		} catch (RepositoryException e) {
678 			throw new ConnectException("Unable to create tag instance " + tagKey + " for tag " + tagParent, e);
679 		}
680 	}
681 
682 	protected String getTagKeyPropName(Node tagParent) {
683 		try {
684 			if (tagParent.hasProperty(ResourcesNames.RESOURCES_TAG_CODE_PROP_NAME))
685 				return tagParent.getProperty(ResourcesNames.RESOURCES_TAG_CODE_PROP_NAME).getString();
686 			else
687 				return ConnectJcrUtils.getLocalJcrItemName(Property.JCR_TITLE);
688 		} catch (RepositoryException e) {
689 			throw new ConnectException("unable to retrieve key property name for " + tagParent);
690 		}
691 	}
692 
693 	// Helper that will throw an exception if the tag parent is not found
694 	private Node getExistingTagLikeParent(Session session, String tagId) {
695 		Node tagInstanceParent = getTagLikeResourceParent(session, tagId);
696 		if (tagInstanceParent == null)
697 			throw new ConnectException(
698 					"Tag like resource with ID " + tagId + " does not exist. Cannot process current action.");
699 		else
700 			return tagInstanceParent;
701 	}
702 
703 	private NodeIterator getRegisteredTags(Node tagParent, String filter) {
704 		try {
705 			String nodeType = tagParent.getProperty(ResourcesNames.RESOURCES_TAG_INSTANCE_TYPE).getString();
706 			String queryStr = "select * from [" + nodeType + "] as nodes where ISDESCENDANTNODE('" + tagParent.getPath()
707 					+ "')";
708 			if (notEmpty(filter))
709 				queryStr += " AND LOWER(nodes.[" + Property.JCR_TITLE + "]) like \"%" + filter.toLowerCase() + "%\"";
710 			queryStr += " ORDER BY nodes.[" + Property.JCR_TITLE + "]";
711 
712 			Query query = tagParent.getSession().getWorkspace().getQueryManager().createQuery(queryStr, Query.JCR_SQL2);
713 			return query.execute().getNodes();
714 		} catch (RepositoryException re) {
715 			throw new ConnectException("Unable to get values for " + tagParent + " with filter " + filter, re);
716 		}
717 	}
718 
719 	@Override
720 	public boolean canCreateTag(Session session) {
721 		// TODO Rather store this info in the instance configuration
722 		String prop = System.getProperty(ConnectConstants.SYS_PROP_ID_PREVENT_TAG_ADDITION);
723 		return !"true".equals(prop) || CurrentUser.isInRole(ResourcesRole.editor.dn());
724 	}
725 }