001/* 002 * Copyright 2014 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.cms.tag; 017 018import java.text.Normalizer; 019import java.util.ArrayList; 020import java.util.Collection; 021import java.util.Collections; 022import java.util.HashMap; 023import java.util.List; 024import java.util.Map; 025import java.util.Set; 026 027import javax.jcr.RepositoryException; 028 029import org.apache.avalon.framework.component.Component; 030import org.apache.avalon.framework.logger.AbstractLogEnabled; 031import org.apache.avalon.framework.service.ServiceException; 032import org.apache.avalon.framework.service.ServiceManager; 033import org.apache.avalon.framework.service.Serviceable; 034import org.apache.commons.lang3.StringUtils; 035 036import org.ametys.cms.tag.jcr.AbstractJCRTagProvider; 037import org.ametys.cms.tag.jcr.AbstractJCRTagsDAO; 038import org.ametys.cms.tag.jcr.JCRTag; 039import org.ametys.core.ui.Callable; 040import org.ametys.core.util.I18nUtils; 041import org.ametys.runtime.i18n.I18nizableText; 042 043/** 044 * DAO for manipulating tags 045 */ 046public abstract class AbstractTagsDAO extends AbstractLogEnabled implements Serviceable, Component 047{ 048 /** The tag provider extension point */ 049 protected AbstractTagProviderExtensionPoint<? extends Tag> _tagProviderExtPt; 050 /** The I18n utils */ 051 protected I18nUtils _i18nUtils; 052 053 @SuppressWarnings("unchecked") 054 @Override 055 public void service(ServiceManager manager) throws ServiceException 056 { 057 _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE); 058 _tagProviderExtPt = (AbstractTagProviderExtensionPoint< ? extends Tag>) manager.lookup(getTagProviderEPRole()); 059 } 060 061 /** 062 * Get the tag provider extension point role 063 * @return the tag provider extension point role 064 */ 065 public abstract String getTagProviderEPRole(); 066 067 /** 068 * Get the paths of given tags 069 * @param tagNames The name of tags 070 * @param contextualParameters Contextual parameters 071 * @return {String} path 072 */ 073 @Callable 074 public List<String> getTagPaths(List<String> tagNames, Map<String, Object> contextualParameters) 075 { 076 List<String> paths = new ArrayList<>(); 077 if (tagNames != null) 078 { 079 for (String tagName : tagNames) 080 { 081 if (tagName.startsWith("provider_")) 082 { 083 paths.add(tagName); 084 } 085 else 086 { 087 String tagPath = getFullPath(tagName, contextualParameters); 088 if (tagPath != null) 089 { 090 paths.add(tagPath); 091 } 092 093 } 094 } 095 } 096 097 return paths; 098 } 099 100 /** 101 * Get the full path of a tag, with its provider 102 * @param tagName The tag name 103 * @param contextualParameters Contextual parameters 104 * @return the full path of tag or null if not found 105 */ 106 protected String getFullPath (String tagName, Map<String, Object> contextualParameters) 107 { 108 Set<String> tagProviders = _tagProviderExtPt.getExtensionsIds(); 109 110 for (String id : tagProviders) 111 { 112 TagProvider<? extends Tag> tagProvider = _tagProviderExtPt.getExtension(id); 113 if (tagProvider.hasTag(tagName, contextualParameters)) 114 { 115 Tag tag = tagProvider.getTag(tagName, contextualParameters); 116 return "provider_" + tagProvider.getId() + getPath(tag); 117 } 118 } 119 120 // Not found 121 return null; 122 } 123 124 /** 125 * Get a tag by its name 126 * @param tagName The tag name 127 * @param contextualParameters Contextual parameters 128 * @return The tag or null if not found 129 */ 130 public Tag getTag (String tagName, Map<String, Object> contextualParameters) 131 { 132 Set<String> tagProviders = _tagProviderExtPt.getExtensionsIds(); 133 134 for (String id : tagProviders) 135 { 136 TagProvider<? extends Tag> tagProvider = _tagProviderExtPt.getExtension(id); 137 if (tagProvider.hasTag(tagName, contextualParameters)) 138 { 139 return tagProvider.getTag(tagName, contextualParameters); 140 } 141 } 142 return null; 143 } 144 145 /** 146 * Get the path of a tag inside its provider. 147 * The path is composed of tags name and '/' as a separator 148 * @param tag The tag 149 * @return The path 150 */ 151 protected String getPath (Tag tag) 152 { 153 return tag == null ? "" : getPath(tag.getParent()) + "/" + tag.getName(); 154 } 155 156 /** 157 * Get the title of given tags 158 * @param tagNames The name of tags 159 * @param contextualParameters Contextual parameters 160 * @return the labels 161 */ 162 @Callable 163 public Map<String, Object> getTagsTitle(List<String> tagNames, Map<String, Object> contextualParameters) 164 { 165 Map<String, Object> result = new HashMap<>(); 166 167 Set<String> tagProviders = _tagProviderExtPt.getExtensionsIds(); 168 169 List<I18nizableText> labels = new ArrayList<>(); 170 171 for (String tagName : tagNames) 172 { 173 if (tagName.startsWith("provider_")) 174 { 175 TagProvider<? extends Tag> tagProvider = _tagProviderExtPt.getExtension(tagName.substring("provider_".length())); 176 labels.add(tagProvider.getLabel()); 177 } 178 else 179 { 180 for (String id : tagProviders) 181 { 182 TagProvider<? extends Tag> tagProvider = _tagProviderExtPt.getExtension(id); 183 if (tagProvider.hasTag(tagName, contextualParameters)) 184 { 185 Tag tag = tagProvider.getTag(tagName, contextualParameters); 186 labels.add(tag.getTitle()); 187 } 188 } 189 } 190 } 191 192 result.put("titles", labels); 193 return result; 194 } 195 196 /** 197 * Get the path of node which match filter regexp 198 * @param value the value to match 199 * @param contextualParameters Contextual parameters 200 * @return the matching paths 201 */ 202 @Callable 203 public List<String> filterTagsByRegExp(String value, Map<String, Object> contextualParameters) 204 { 205 List<String> matchingPaths = new ArrayList<>(); 206 207 String toMatch = Normalizer.normalize(value.toLowerCase(), Normalizer.Form.NFD).replaceAll("[\\p{InCombiningDiacriticalMarks}]", "").trim(); 208 209 Set<String> extensionsIds = _tagProviderExtPt.getExtensionsIds(); 210 for (String id : extensionsIds) 211 { 212 TagProvider<? extends Tag> tagProvider = _tagProviderExtPt.getExtension(id); 213 for (Tag tag : tagProvider.getTags(contextualParameters).values()) 214 { 215 _getMatchingTag(toMatch, tagProvider, tag, matchingPaths); 216 } 217 } 218 219 return matchingPaths; 220 } 221 222 /** 223 * Get the path of node which match filter regexp 224 * @param filter the value to match 225 * @param tagNames the list of tag's name to search inside 226 * @param contextualParameters Contextual parameters 227 * @return the matching paths 228 */ 229 @Callable 230 public List<String> filterTagsFromListByRegExp(String filter, List<String> tagNames, Map<String, Object> contextualParameters) 231 { 232 if (StringUtils.isEmpty(filter)) 233 { 234 return getTagPaths(tagNames, contextualParameters); 235 } 236 237 if (tagNames != null && tagNames.size() > 0) 238 { 239 List<String> matchingPaths = new ArrayList<>(); 240 241 String toMatch = Normalizer.normalize(filter.toLowerCase(), Normalizer.Form.NFD).replaceAll("[\\p{InCombiningDiacriticalMarks}]", "").trim(); 242 243 for (String tagName : tagNames) 244 { 245 Tag tag = getTag(tagName, contextualParameters); 246 if (tag != null) 247 { 248 String title = _i18nUtils.translate(tag.getTitle()); 249 String normalizedName = Normalizer.normalize(title.toLowerCase(), Normalizer.Form.NFD).replaceAll("[\\p{InCombiningDiacriticalMarks}]", ""); 250 if (normalizedName.contains(toMatch)) 251 { 252 matchingPaths.add(getFullPath(tagName, contextualParameters)); 253 } 254 } 255 } 256 257 return matchingPaths; 258 } 259 else 260 { 261 return filterTagsByRegExp(filter, contextualParameters); 262 } 263 } 264 265 /** 266 * Get paths of tag which match filter regexp 267 * @param value the value to match 268 * @param tagProvider the tag provider 269 * @param tag the current tag 270 * @param matchingPaths the matching paths 271 */ 272 private void _getMatchingTag(String value, TagProvider<? extends Tag> tagProvider, Tag tag, List<String> matchingPaths) 273 { 274 String title = _i18nUtils.translate(tag.getTitle()); 275 276 if (title != null) 277 { 278 String normalizedName = Normalizer.normalize(title.toLowerCase(), Normalizer.Form.NFD).replaceAll("[\\p{InCombiningDiacriticalMarks}]", ""); 279 280 if (normalizedName.contains(value)) 281 { 282 matchingPaths.add("provider_" + tagProvider.getId() + getPath(tag)); 283 } 284 } 285 286 for (Tag child : tag.getTags().values()) 287 { 288 _getMatchingTag(value, tagProvider, child, matchingPaths); 289 } 290 } 291 292 /** 293 * Test if tags exists 294 * @param tagNames The tag names to test 295 * @param onlyCustomTags If true, return only custom tags 296 * @param otherParameters the other parameters 297 * @param contextualParameters Contextual parameters 298 * @return The list of tag names without the invalid values. 299 */ 300 @Callable 301 public List<String> checkTags(List<String> tagNames, boolean onlyCustomTags, Map<String, Object> otherParameters, Map<String, Object> contextualParameters) 302 { 303 List<String> existingTagNames = new ArrayList<>(); 304 List<TagProvider<? extends Tag>> tagProviders = new ArrayList<>(); 305 306 if (onlyCustomTags) 307 { 308 tagProviders.addAll(getCustomTagProvider()); 309 } 310 else 311 { 312 Set<String> extensionsIds = _tagProviderExtPt.getExtensionsIds(); 313 for (String id : extensionsIds) 314 { 315 tagProviders.add(_tagProviderExtPt.getExtension(id)); 316 } 317 } 318 319 for (String tagName : tagNames) 320 { 321 if (tagName.startsWith("provider_")) 322 { 323 String providerId = tagName.substring("provider_".length()); 324 TagProvider<? extends Tag> tagProvider = _tagProviderExtPt.getExtension(providerId); 325 326 if (tagProvider != null) 327 { 328 existingTagNames.add(tagName); 329 } 330 } 331 else 332 { 333 for (TagProvider<? extends Tag> tagProvider : tagProviders) 334 { 335 if (tagProvider.hasTag(tagName, contextualParameters)) 336 { 337 String filteredTagName = _getFilteredTagName(tagProvider, tagName, otherParameters, contextualParameters); 338 if (StringUtils.isNotBlank(filteredTagName)) 339 { 340 existingTagNames.add(filteredTagName); 341 break; 342 } 343 } 344 } 345 } 346 } 347 348 return existingTagNames; 349 } 350 351 /** 352 * Get filtered tag name 353 * @param tagProvider the tag provider 354 * @param tagName the tag name 355 * @param otherParameters the other parameters 356 * @param contextualParameters the contextual parameters 357 * @return the tag name if it match 358 */ 359 protected String _getFilteredTagName(TagProvider<? extends Tag> tagProvider, String tagName, Map<String, Object> otherParameters, Map<String, Object> contextualParameters) 360 { 361 return tagName; 362 } 363 364 /** 365 * Get the list of custom tag provider 366 * @return the list of custom tag provider 367 */ 368 protected abstract List<TagProvider<? extends Tag>> getCustomTagProvider(); 369 370 /** 371 * Add tags from FO 372 * @param tagNames The name of tags to add 373 * @return The new tags descriptions 374 */ 375 public List<Map<String, Object>> addTags(String[] tagNames) 376 { 377 try 378 { 379 List<Map<String, Object>> tags = new ArrayList<>(); 380 381 for (String tagName : tagNames) 382 { 383 String filteredName = Normalizer.normalize(tagName.toLowerCase(), Normalizer.Form.NFD).replaceAll("[\\p{InCombiningDiacriticalMarks}]", "").trim(); 384 filteredName = filteredName.replaceAll("œ", "oe").replaceAll("æ", "ae").replaceAll("[^a-z0-9]", "_").replaceAll("_+", "_"); 385 386 JCRTag tag = _getTagJCRDAO().addTag(null, filteredName.toUpperCase(), tagName, "", Collections.emptyMap(), Collections.emptyMap()); 387 tags.add(_tagToJSON(tag)); 388 } 389 390 // Clear the request cache after tag added 391 for (String id : _tagProviderExtPt.getExtensionsIds()) 392 { 393 TagProvider tagProvider = _tagProviderExtPt.getExtension(id); 394 if (tagProvider instanceof AbstractJCRTagProvider) 395 { 396 ((AbstractJCRTagProvider) tagProvider).clearCache(); 397 } 398 } 399 400 return tags; 401 } 402 catch (RepositoryException e) 403 { 404 throw new IllegalStateException("Cannot create tags", e); 405 } 406 } 407 408 /** 409 * Get the tag JCR DAO 410 * @return the tag JCR DAO 411 */ 412 protected abstract AbstractJCRTagsDAO _getTagJCRDAO(); 413 414 /** 415 * Get tags to json 416 * @param tags the tags 417 * @return the tags to json 418 */ 419 protected List<Map<String, Object>> _tagsToJSON(Collection< ? extends Tag> tags) 420 { 421 List<Map<String, Object>> result = new ArrayList<>(); 422 423 for (Tag tag : tags) 424 { 425 result.add(tagToJSON(tag)); 426 427 Map<String, ? extends Tag> subTags = tag.getTags(); 428 if (subTags != null) 429 { 430 result.addAll(_tagsToJSON(subTags.values())); 431 } 432 } 433 434 return result; 435 } 436 437 /** 438 * JCR tag to json 439 * @param tag the jcr tag 440 * @return the jcr tag to json 441 */ 442 protected Map<String, Object> _tagToJSON(JCRTag tag) 443 { 444 Map<String, Object> tagInfo = new HashMap<>(); 445 tagInfo.put("id", tag.getId()); 446 tagInfo.put("name", tag.getName()); 447 tagInfo.put("text", tag.getTitle()); 448 return tagInfo; 449 } 450 451 /** 452 * Tag to json 453 * @param tag the tag 454 * @return the tag to json 455 */ 456 public Map<String, Object> tagToJSON(Tag tag) 457 { 458 Map<String, Object> tagInfo = new HashMap<>(); 459 tagInfo.put("id", tag.getId()); 460 tagInfo.put("name", tag.getName()); 461 tagInfo.put("text", tag.getTitle()); 462 return tagInfo; 463 } 464}