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