001/* 002 * Copyright 2015 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.plugins.glossary; 017 018import java.text.Normalizer; 019import java.text.Normalizer.Form; 020import java.util.ArrayList; 021import java.util.Collections; 022import java.util.HashMap; 023import java.util.List; 024import java.util.Map; 025import java.util.Set; 026 027import org.apache.avalon.framework.component.Component; 028import org.apache.avalon.framework.logger.AbstractLogEnabled; 029import org.apache.avalon.framework.service.ServiceException; 030import org.apache.avalon.framework.service.ServiceManager; 031import org.apache.avalon.framework.service.Serviceable; 032import org.apache.commons.lang.StringUtils; 033 034import org.ametys.core.ui.Callable; 035import org.ametys.plugins.glossary.theme.ThemesDAO; 036import org.ametys.plugins.repository.AmetysObjectIterable; 037import org.ametys.plugins.repository.AmetysObjectResolver; 038import org.ametys.plugins.repository.AmetysRepositoryException; 039import org.ametys.plugins.repository.ModifiableAmetysObject; 040import org.ametys.plugins.repository.ModifiableTraversableAmetysObject; 041import org.ametys.plugins.repository.TraversableAmetysObject; 042import org.ametys.plugins.repository.UnknownAmetysObjectException; 043import org.ametys.plugins.repository.jcr.NameHelper; 044import org.ametys.runtime.i18n.I18nizableText; 045import org.ametys.web.repository.site.SiteManager; 046 047/** 048 * DAO for manipulating glossary. 049 * 050 */ 051public class GlossaryDAO extends AbstractLogEnabled implements Serviceable, Component 052{ 053 /** The Avalon role */ 054 public static final String ROLE = GlossaryDAO.class.getName(); 055 056 /** The site manager */ 057 protected SiteManager _siteManager; 058 059 /** Ametys object resolver */ 060 protected AmetysObjectResolver _resolver; 061 062 /** The themes DAO */ 063 ThemesDAO _themeDAO; 064 065 @Override 066 public void service(ServiceManager manager) throws ServiceException 067 { 068 _siteManager = (SiteManager) manager.lookup(SiteManager.ROLE); 069 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 070 _themeDAO = (ThemesDAO) manager.lookup(ThemesDAO.ROLE); 071 } 072 073 /** 074 * Get the word definitions 075 * @param siteName The site name 076 * @param lang The language 077 * @return The word definitions 078 */ 079 @Callable (rights = "Glossary_Rights_Definitions_Handle") 080 public Map<String, Object> getDefinitions(String siteName, String lang) 081 { 082 Map<String, Object> result = new HashMap<>(); 083 084 List<Map<String, Object>> definitions = new ArrayList<>(); 085 086 TraversableAmetysObject definitionsNode = GlossaryHelper.getDefinitionsNode(_siteManager.getSite(siteName), lang); 087 088 try (AmetysObjectIterable<DefaultDefinition> wordDefinitions = definitionsNode.getChildren()) 089 { 090 for (DefaultDefinition wordDefinition : wordDefinitions) 091 { 092 definitions.add(getDefinition(wordDefinition)); 093 } 094 } 095 096 result.put("definitions", definitions); 097 098 return result; 099 } 100 101 /** 102 * Get properties of a word definition 103 * @param id the id of definition 104 * @return the properties 105 */ 106 @Callable (rights = "Glossary_Rights_Definitions_Handle") 107 public Map<String, Object> getDefinition (String id) 108 { 109 DefaultDefinition wordDefinition = _resolver.resolveById(id); 110 111 return getDefinition(wordDefinition); 112 } 113 114 /** 115 * Get the definition in JSON 116 * @param wordDefinition the word definition 117 * @return the JSON for the word definition 118 */ 119 public Map<String, Object> getDefinition (DefaultDefinition wordDefinition) 120 { 121 Map<String, Object> properties = new HashMap<>(); 122 123 properties.put("id", wordDefinition.getId()); 124 properties.put("lang", wordDefinition.getLanguage()); 125 properties.put("siteName", wordDefinition.getSiteName()); 126 properties.put("word", wordDefinition.getWord()); 127 String firstLetter = Normalizer.normalize(wordDefinition.getWord().substring(0, 1), Form.NFD).replaceAll("\\p{InCombiningDiacriticalMarks}+", ""); 128 properties.put("firstLetter", firstLetter); 129 properties.put("variants", wordDefinition.getVariants()); 130 properties.put("content", wordDefinition.getContent()); 131 properties.put("displayOnText", wordDefinition.displayOnText()); 132 133 // Themes 134 List<Map<String, Object>> themesList = new ArrayList<>(); 135 for (String themeId : wordDefinition.getThemes()) 136 { 137 try 138 { 139 I18nizableText themeTitle = _themeDAO.getThemeTitle(themeId, wordDefinition.getSiteName(), wordDefinition.getLanguage()); 140 Map<String, Object> themeData = new HashMap<>(); 141 themeData.put("id", themeId); 142 themeData.put("label", themeTitle); 143 themesList.add(themeData); 144 } 145 catch (UnknownAmetysObjectException e) 146 { 147 // Theme does not exist anymore 148 } 149 } 150 151 properties.put("themes", themesList); 152 153 return properties; 154 } 155 156 /** 157 * Creates a word definition. 158 * @param word The word 159 * @param variants The variants 160 * @param content The content 161 * @param themes the themes 162 * @param display The value of displayOnText 163 * @param siteName The site name 164 * @param language The language 165 * @return The id of the created word, or an error 166 */ 167 @Callable (rights = "Glossary_Rights_Definitions_Handle") 168 public Map<String, String> createDefinition (String word, String variants, String content, List<String> themes, Boolean display, String siteName, String language) 169 { 170 Map<String, String> result = new HashMap<>(); 171 172 String capitalizedWord = StringUtils.capitalize(word); 173 List<String> variantList = new ArrayList<>(); 174 if (StringUtils.isNotBlank(variants)) 175 { 176 for (String variant : StringUtils.split(variants, ",")) 177 { 178 String trimVariant = variant.trim(); 179 if (StringUtils.isNotEmpty(trimVariant)) 180 { 181 variantList.add(StringUtils.capitalize(trimVariant)); 182 } 183 } 184 } 185 186 // Check that the word doesn't already exist. 187 if (_wordExists(capitalizedWord, siteName, language)) 188 { 189 result.put("error", "word-already-exists"); 190 return result; 191 } 192 193 // Check that none of the variant already exists. 194 for (String variant : variantList) 195 { 196 if (_wordExists(variant, siteName, language)) 197 { 198 result.put("error", "word-already-exists"); 199 return result; 200 } 201 } 202 203 ModifiableTraversableAmetysObject rootNode = GlossaryHelper.getDefinitionsNode(_siteManager.getSite(siteName), language); 204 205 String originalName = NameHelper.filterName(capitalizedWord); 206 207 // Find unique name 208 String name = originalName; 209 int index = 2; 210 while (rootNode.hasChild(name)) 211 { 212 name = originalName + "-" + (index++); 213 } 214 215 DefaultDefinition def = rootNode.createChild(name, DefaultDefinitionFactory.DEFINITION_NODE_TYPE); 216 def.setWord(capitalizedWord); 217 def.setVariants(variantList); 218 def.setContent(content); 219 def.setThemes(themes.toArray(new String[themes.size()])); 220 def.setDisplayOnText(display); 221 222 rootNode.saveChanges(); 223 224 result.put("id", def.getId()); 225 226 return result; 227 } 228 229 /** 230 * Edits a word definition. 231 * @param id The id of the word to edit 232 * @param word The new word 233 * @param variants The new variants 234 * @param content The new content 235 * @param themes the themes 236 * @param display The new value of displayOnText 237 * @param siteName The site name 238 * @param language The language 239 * @return The id of the edited word, or an error 240 */ 241 @Callable (rights = "Glossary_Rights_Definitions_Handle") 242 public Map<String, String> editDefinition (String id, String word, String variants, String content, List<String> themes, Boolean display, String siteName, String language) 243 { 244 Map<String, String> result = new HashMap<>(); 245 246 String capitalizedWord = StringUtils.capitalize(word); 247 List<String> variantList = new ArrayList<>(); 248 if (StringUtils.isNotBlank(variants)) 249 { 250 for (String variant : StringUtils.split(variants, ",")) 251 { 252 String trimVariant = variant.trim(); 253 if (StringUtils.isNotEmpty(trimVariant)) 254 { 255 variantList.add(StringUtils.capitalize(variant.trim())); 256 } 257 } 258 } 259 260 try 261 { 262 DefaultDefinition definition = _resolver.resolveById(id); 263 264 Set<String> allForms = definition.getAllForms(); 265 266 // If the word was changed, check that the new word doesn't already exist. 267 if (!allForms.contains(word) && _wordExists(word, siteName, language)) 268 { 269 return Collections.singletonMap("error", "word-already-exists"); 270 } 271 272 // If the word was changed, check that none of the variant already exists. 273 for (String variant : variantList) 274 { 275 if (!allForms.contains(variant) && _wordExists(variant, siteName, language)) 276 { 277 return Collections.singletonMap("error", "word-already-exists"); 278 } 279 } 280 281 definition.setWord(capitalizedWord.trim()); 282 definition.setVariants(variantList); 283 definition.setContent(content); 284 definition.setThemes(themes.toArray(new String[themes.size()])); 285 definition.setDisplayOnText(display); 286 287 definition.saveChanges(); 288 289 result.put("id", definition.getId()); 290 } 291 catch (UnknownAmetysObjectException e) 292 { 293 result.put("error", "unknown-definition"); 294 getLogger().error("Unable to edit definition of id '" + id + ", because it does not not exist.", e); 295 } 296 297 return result; 298 } 299 300 /** 301 * Deletes word definitions. 302 * @param ids The ids of definitions to delete 303 * @return The ids of the deleted words, the ids of the unknown words, and maybe an error 304 */ 305 @Callable (rights = "Glossary_Rights_Definitions_Handle") 306 public Map<String, Object> deleteDefinitions (ArrayList<String> ids) 307 { 308 Map<String, Object> result = new HashMap<>(); 309 List<String> deletedDefinitions = new ArrayList<>(); 310 List<String> unknownIds = new ArrayList<>(); 311 312 for (String id : ids) 313 { 314 try 315 { 316 DefaultDefinition definition = _resolver.resolveById(id); 317 318 ModifiableAmetysObject parent = definition.getParent(); 319 definition.remove(); 320 321 parent.saveChanges(); 322 323 deletedDefinitions.add(id); 324 } 325 catch (UnknownAmetysObjectException e) 326 { 327 result.put("error", "unknown-definition"); 328 getLogger().error("Unable to delete the definition of id '" + id + ", because it does not exist.", e); 329 unknownIds.add(id); 330 } 331 } 332 333 result.put("ids", deletedDefinitions); 334 result.put("unknown-ids", unknownIds); 335 336 return result; 337 } 338 339 /** 340 * Tests if the specified word exists in the glossary. 341 * @param word The word to test. 342 * @param siteName The site name. 343 * @param language The language. 344 * @return true if the word exists. 345 * @throws AmetysRepositoryException if a repository error occurs. 346 */ 347 protected boolean _wordExists(String word, String siteName, String language) throws AmetysRepositoryException 348 { 349 String xpathQuery = GlossaryHelper.getWordExistsQuery(siteName, language, word); 350 351 try (AmetysObjectIterable<DefaultDefinition> defs = _resolver.query(xpathQuery)) 352 { 353 return defs.iterator().hasNext(); 354 } 355 } 356}