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