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.linkdirectory.theme; 017 018import java.util.ArrayList; 019import java.util.HashMap; 020import java.util.HashSet; 021import java.util.LinkedHashMap; 022import java.util.List; 023import java.util.Map; 024import java.util.Set; 025 026import org.apache.avalon.framework.component.Component; 027import org.apache.avalon.framework.logger.AbstractLogEnabled; 028import org.apache.avalon.framework.service.ServiceException; 029import org.apache.avalon.framework.service.ServiceManager; 030import org.apache.avalon.framework.service.Serviceable; 031import org.apache.commons.lang3.StringUtils; 032 033import org.ametys.core.observation.Event; 034import org.ametys.core.observation.ObservationManager; 035import org.ametys.core.ui.Callable; 036import org.ametys.core.user.CurrentUserProvider; 037import org.ametys.plugins.explorer.ObservationConstants; 038import org.ametys.plugins.linkdirectory.DirectoryEvents; 039import org.ametys.plugins.linkdirectory.DirectoryHelper; 040import org.ametys.plugins.linkdirectory.Theme; 041import org.ametys.plugins.linkdirectory.repository.DefaultLink; 042import org.ametys.plugins.linkdirectory.repository.DefaultTheme; 043import org.ametys.plugins.linkdirectory.repository.DefaultThemeFactory; 044import org.ametys.plugins.repository.AmetysObjectIterable; 045import org.ametys.plugins.repository.AmetysObjectIterator; 046import org.ametys.plugins.repository.AmetysObjectResolver; 047import org.ametys.plugins.repository.AmetysRepositoryException; 048import org.ametys.plugins.repository.ModifiableAmetysObject; 049import org.ametys.plugins.repository.ModifiableTraversableAmetysObject; 050import org.ametys.plugins.repository.UnknownAmetysObjectException; 051import org.ametys.web.repository.site.Site; 052import org.ametys.web.repository.site.SiteManager; 053 054/** 055 * DAO for manipulating {@link Theme} 056 */ 057public class ThemeDAO extends AbstractLogEnabled implements Serviceable, Component 058{ 059 /** Avalon Role */ 060 public static final String ROLE = ThemeDAO.class.getName(); 061 062 private AmetysObjectResolver _resolver; 063 private ObservationManager _observationManager; 064 private SiteManager _siteManager; 065 private CurrentUserProvider _currentUserProvider; 066 private DirectoryHelper _directoryHelper; 067 068 public void service(ServiceManager manager) throws ServiceException 069 { 070 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 071 _observationManager = (ObservationManager) manager.lookup(ObservationManager.ROLE); 072 _siteManager = (SiteManager) manager.lookup(SiteManager.ROLE); 073 _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE); 074 _directoryHelper = (DirectoryHelper) manager.lookup(DirectoryHelper.ROLE); 075 } 076 077 /** 078 * Get themes 079 * @param ids the id of themes 080 * @return the retrieving themes and the unknown themes 081 */ 082 @Callable 083 public Map<String, Object> getThemes (List<String> ids) 084 { 085 Map<String, Object> result = new HashMap<>(); 086 087 List<Map<String, Object>> themes = new ArrayList<>(); 088 List<String> unknownThemes = new ArrayList<>(); 089 for (String id : ids) 090 { 091 try 092 { 093 themes.add(getTheme(id)); 094 } 095 catch (UnknownAmetysObjectException e) 096 { 097 unknownThemes.add(id); 098 } 099 } 100 101 result.put("themes", themes); 102 result.put("unknown-themes", unknownThemes); 103 104 return result; 105 106 } 107 108 /** 109 * Convert a {@link Theme} to JSON object 110 * @param themeId the theme's id 111 * @return The theme data 112 * @throws UnknownAmetysObjectException if the theme does not exist. 113 */ 114 @Callable 115 public Map<String, Object> getTheme (String themeId) throws UnknownAmetysObjectException 116 { 117 DefaultTheme theme = _resolver.resolveById(themeId); 118 119 HashMap<String, Object> jsonObject = new LinkedHashMap<>(); 120 jsonObject.put("id", theme.getId()); 121 jsonObject.put("label", StringUtils.defaultString(theme.getLabel())); 122 jsonObject.put("name", theme.getName()); 123 jsonObject.put("lang", theme.getLanguage()); 124 125 return jsonObject; 126 } 127 128 /** 129 * Create a new theme 130 * @param siteName the site name 131 * @param language the site language 132 * @param originalLabel the label of the theme 133 * @return the id of created theme 134 */ 135 @Callable 136 public Map<String, Object> createTheme(String siteName, String language, String originalLabel) 137 { 138 Map<String, Object> result = new HashMap<>(); 139 Site site = _siteManager.getSite(siteName); 140 141 ModifiableTraversableAmetysObject rootNode = _directoryHelper.getThemesNode(site, language); 142 143 // Find unique name 144 String label = originalLabel; 145 if (themeExists(label, siteName, language, null)) 146 { 147 result.put("already-exists", true); 148 getLogger().error("Unable to create a theme with label '" + label + ", because the new label is already used."); 149 return result; 150 } 151 int index = 2; 152 while (rootNode.hasChild(_directoryHelper.normalizeString(label))) 153 { 154 label = originalLabel + "-" + (index++); 155 } 156 157 DefaultTheme theme = rootNode.createChild(_directoryHelper.normalizeString(label), DefaultThemeFactory.THEME_NODE_TYPE); 158 159 theme.setLabel(originalLabel); 160 161 rootNode.saveChanges(); 162 163 // Notify listeners 164 Map<String, Object> eventParams = new HashMap<>(); 165 eventParams.put(ObservationConstants.ARGS_ID, theme.getId()); 166 eventParams.put(ObservationConstants.ARGS_PARENT_ID, rootNode.getId()); 167 eventParams.put(ObservationConstants.ARGS_NAME, theme.getName()); 168 eventParams.put(ObservationConstants.ARGS_PATH, theme.getPath()); 169 170 _observationManager.notify(new Event(DirectoryEvents.THEME_CREATED, _currentUserProvider.getUser(), eventParams)); 171 172 result.put("id", theme.getId()); 173 return result; 174 } 175 176 /** 177 * Delete themes 178 * @param ids the id of themes to delete 179 * @return the list of modified links, or an error code 180 */ 181 @Callable 182 public Map<String, Object> deleteTheme(List<String> ids) 183 { 184 Map<String, Object> result = new HashMap<>(); 185 result.put("deleted-themes", new ArrayList<>()); 186 result.put("unknown-themes", new ArrayList<>()); 187 result.put("modified-links", new ArrayList<>()); 188 189 for (String id : ids) 190 { 191 try 192 { 193 DefaultTheme theme = _resolver.resolveById(id); 194 195 String siteName = theme.getSiteName(); 196 String language = theme.getLanguage(); 197 String name = theme.getName(); 198 String path = theme.getPath(); 199 200 ModifiableAmetysObject parent = theme.getParent(); 201 theme.remove(); 202 203 parent.saveChanges(); 204 205 @SuppressWarnings("unchecked") 206 List<String> deletedThemes = (List<String>) result.get("deleted-themes"); 207 deletedThemes.add(id); 208 209 // Remove references 210 @SuppressWarnings("unchecked") 211 List<String> modifiedLinks = (List<String>) result.get("modified-links"); 212 modifiedLinks.addAll(_deleteThemeRefIntoLinks(siteName, language, id)); 213 214 // Notify listeners 215 Map<String, Object> eventParams = new HashMap<>(); 216 eventParams.put(ObservationConstants.ARGS_ID, id); 217 eventParams.put(ObservationConstants.ARGS_NAME, name); 218 eventParams.put(ObservationConstants.ARGS_PATH, path); 219 eventParams.put("siteName", siteName); 220 eventParams.put("language", language); 221 _observationManager.notify(new Event(DirectoryEvents.THEME_DELETED, _currentUserProvider.getUser(), eventParams)); 222 } 223 catch (UnknownAmetysObjectException e) 224 { 225 @SuppressWarnings("unchecked") 226 List<String> unknownThemes = (List<String>) result.get("unknown-themes"); 227 unknownThemes.add(id); 228 getLogger().error("Unable to delete the theme of id '" + id + ", because it does not exist.", e); 229 } 230 } 231 232 return result; 233 } 234 235 /** 236 * Update a theme 237 * @param siteName the site name 238 * @param language the site language 239 * @param id the theme id 240 * @param label the new theme label 241 * @return the list of modified links, or an error code 242 */ 243 @Callable 244 public Map<String, Object> updateTheme(String siteName, String language, String id, String label) 245 { 246 Map<String, Object> result = new HashMap<>(); 247 result.put("modified-links", new HashSet<>()); 248 249 try 250 { 251 DefaultTheme theme = _resolver.resolveById(id); 252 253 // If the word was changed, check that the new word doesn't already exist. 254 if (!theme.getLabel().equals(label) && themeExists(label, siteName, language, id)) 255 { 256 result.put("already-exists", true); 257 getLogger().error("Unable to update the theme of id '" + id + ", because the new label is already used."); 258 return result; 259 } 260 261 262 theme.setLabel(label); 263 theme.saveChanges(); 264 265 result.put("id", theme.getId()); 266 result.put("label", theme.getLabel()); 267 268 @SuppressWarnings("unchecked") 269 Set<String> modifiedLinks = (Set<String>) result.get("modified-links"); 270 modifiedLinks.addAll(_getReferencingLinks(siteName, language, id)); 271 272 // Notify listeners 273 Map<String, Object> eventParams = new HashMap<>(); 274 eventParams.put(ObservationConstants.ARGS_ID, theme.getId()); 275 eventParams.put(ObservationConstants.ARGS_NAME, theme.getName()); 276 eventParams.put(ObservationConstants.ARGS_PATH, theme.getPath()); 277 278 _observationManager.notify(new Event(DirectoryEvents.THEME_MODIFIED, _currentUserProvider.getUser(), eventParams)); 279 } 280 catch (UnknownAmetysObjectException e) 281 { 282 result.put("error", "unknown-theme"); 283 getLogger().error("Unable to delete the theme of id '" + id + ", because it does not exist.", e); 284 } 285 286 return result; 287 } 288 289 private Set<String> _getReferencingLinks (String siteName, String lang, String themeId) 290 { 291 Set<String> modifiedLinks = new HashSet<>(); 292 293 String xPathQuery = _directoryHelper.getLinksQuery(siteName, lang, new ThemeExpression(themeId)); 294 295 AmetysObjectIterable<DefaultLink> links = _resolver.query(xPathQuery); 296 for (DefaultLink link : links) 297 { 298 modifiedLinks.add(link.getId()); 299 } 300 301 return modifiedLinks; 302 } 303 304 private Set<String> _deleteThemeRefIntoLinks (String siteName, String lang, String themeId) 305 { 306 Set<String> modifiedLinks = new HashSet<>(); 307 308 String xPathQuery = _directoryHelper.getLinksQuery(siteName, lang, new ThemeExpression(themeId)); 309 310 AmetysObjectIterable<DefaultLink> links = _resolver.query(xPathQuery); 311 312 for (DefaultLink link : links) 313 { 314 link.removeTheme(themeId); 315 link.saveChanges(); 316 modifiedLinks.add(link.getId()); 317 } 318 319 return modifiedLinks; 320 } 321 322 /** 323 * Test if a link with the specified url exists in the directory. 324 * @param label the url to test. 325 * @param siteName the site name. 326 * @param language the language. 327 * @param id id of the current theme to avoid (null if no theme to avoid) 328 * @return true if the link exists. 329 * @throws AmetysRepositoryException if a repository error occurs. 330 */ 331 protected boolean themeExists(String label, String siteName, String language, String id) throws AmetysRepositoryException 332 { 333 String xpathQuery = _directoryHelper.getThemeExistsQuery(siteName, language, label); 334 try (AmetysObjectIterable<DefaultTheme> links = _resolver.query(xpathQuery);) 335 { 336 AmetysObjectIterator<DefaultTheme> iterator = links.iterator(); 337 while (iterator.hasNext()) 338 { 339 DefaultTheme theme = iterator.next(); 340 if (!theme.getId().equals(id)) 341 { 342 return true; 343 } 344 } 345 return false; 346 } 347 } 348}