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}