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}