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