001/*
002 *  Copyright 2017 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.workspaces;
017
018import java.util.HashMap;
019import java.util.Map;
020
021import javax.jcr.Node;
022import javax.jcr.NodeIterator;
023import javax.jcr.RepositoryException;
024
025import org.apache.avalon.framework.context.Context;
026import org.apache.avalon.framework.context.ContextException;
027import org.apache.avalon.framework.context.Contextualizable;
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.lang.StringUtils;
032
033import org.ametys.core.observation.Event;
034import org.ametys.core.observation.ObservationManager;
035import org.ametys.core.right.RightManager;
036import org.ametys.core.user.CurrentUserProvider;
037import org.ametys.core.user.UserManager;
038import org.ametys.core.util.I18nUtils;
039import org.ametys.plugins.core.user.UserHelper;
040import org.ametys.plugins.explorer.resources.ModifiableResourceCollection;
041import org.ametys.plugins.repository.AmetysObject;
042import org.ametys.plugins.repository.AmetysObjectIterable;
043import org.ametys.plugins.repository.AmetysObjectResolver;
044import org.ametys.plugins.repository.AmetysRepositoryException;
045import org.ametys.plugins.repository.ModifiableTraversableAmetysObject;
046import org.ametys.plugins.repository.events.JCREventHelper;
047import org.ametys.plugins.workspaces.project.ProjectConstants;
048import org.ametys.plugins.workspaces.project.ProjectManager;
049import org.ametys.plugins.workspaces.project.modules.WorkspaceModule;
050import org.ametys.plugins.workspaces.project.objects.Project;
051import org.ametys.plugins.workspaces.project.rights.ProjectRightHelper;
052import org.ametys.runtime.i18n.I18nizableText;
053import org.ametys.runtime.model.ElementDefinition;
054import org.ametys.runtime.plugin.component.AbstractLogEnabled;
055import org.ametys.runtime.plugin.component.PluginAware;
056import org.ametys.web.ObservationConstants;
057import org.ametys.web.repository.page.ModifiablePage;
058import org.ametys.web.repository.page.Page;
059import org.ametys.web.repository.page.Page.PageType;
060import org.ametys.web.repository.page.PageDAO;
061import org.ametys.web.repository.site.Site;
062import org.ametys.web.repository.sitemap.Sitemap;
063import org.ametys.web.service.Service;
064import org.ametys.web.service.ServiceExtensionPoint;
065import org.ametys.web.skin.Skin;
066import org.ametys.web.skin.SkinTemplate;
067import org.ametys.web.skin.SkinsManager;
068
069/**
070 * Abstract class for {@link WorkspaceModule} implementation
071 *
072 */
073public abstract class AbstractWorkspaceModule extends AbstractLogEnabled implements WorkspaceModule, Serviceable, Contextualizable, PluginAware
074{
075    /** Project manager */
076    protected ProjectManager _projectManager;
077    /** Project right helper */
078    protected ProjectRightHelper _projectRightHelper;
079    /** User manager */
080    protected UserManager _userManager;
081    /** Ametys resolver */
082    protected AmetysObjectResolver _resolver;
083    /** The rights manager */
084    protected RightManager _rightManager;
085    /** Observer manager. */
086    protected ObservationManager _observationManager;
087    /** The current user provider. */
088    protected CurrentUserProvider _currentUserProvider;
089    /** The users manager */
090    protected UserHelper _userHelper;
091    /** The i18n utils. */
092    protected I18nUtils _i18nUtils;
093    /** The skins manager. */
094    protected SkinsManager _skinsManager;
095    /** The page DAO */
096    protected PageDAO _pageDAO;
097    /** The avalon context */
098    protected Context _context;
099    /** The plugin name */
100    protected String _pluginName;
101    /** The services handler */
102    protected ServiceExtensionPoint _serviceEP;
103    
104    @Override
105    public void service(ServiceManager manager) throws ServiceException
106    {
107        _projectManager = (ProjectManager) manager.lookup(ProjectManager.ROLE);
108        _projectRightHelper = (ProjectRightHelper) manager.lookup(ProjectRightHelper.ROLE);
109        _userManager = (UserManager) manager.lookup(UserManager.ROLE);
110        _userHelper = (UserHelper) manager.lookup(UserHelper.ROLE);
111        _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE);
112        _rightManager = (RightManager) manager.lookup(RightManager.ROLE);
113        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
114        _observationManager = (ObservationManager) manager.lookup(ObservationManager.ROLE);
115        _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE);
116        _skinsManager = (SkinsManager) manager.lookup(SkinsManager.ROLE);
117        _pageDAO = (PageDAO) manager.lookup(PageDAO.ROLE);
118        _serviceEP = (ServiceExtensionPoint) manager.lookup(ServiceExtensionPoint.ROLE);
119    }
120
121    @Override
122    public void contextualize(Context context) throws ContextException
123    {
124        _context = context;
125    }
126    
127    public void setPluginInfo(String pluginName, String featureName, String id)
128    {
129        _pluginName = pluginName;
130    }
131    
132    @Override
133    public void deactivateModule(Project project)
134    {
135        _deletePages(project, getModulePageName());
136        _internalDeactivateModule(project);
137        
138        // Delete root
139        ModifiableResourceCollection moduleRoot = getModuleRoot(project, false);
140        if (moduleRoot != null)
141        {
142            moduleRoot.remove();
143        }
144        
145        // Delete events
146        try
147        {
148            NodeIterator events = JCREventHelper.getEvents(project, getAllowedEventTypes().toArray(new String[]{}));
149            while (events.hasNext())
150            {
151                Node event = (Node) events.next();
152                event.remove();
153            }
154        }
155        catch (RepositoryException e)
156        {
157            getLogger().warn("Unable to delete project '" + project.getName() + "' events for module '" + this.getId() + "'", e);
158        }
159    }
160    
161    @Override
162    public void activateModule(Project project)
163    {
164        for (Site site : project.getSites())
165        {
166            for (Sitemap sitemap : site.getSitemaps())
167            {
168                initializeSitemap(sitemap);
169            }
170        }
171
172        _internalActivateModule(project);
173        
174        // create the resources root node
175        getModuleRoot(project, true);
176    }
177    
178    @Override
179    public void initializeSitemap(Sitemap sitemap)
180    {
181        ModifiablePage page = _createModulePage(sitemap, getModulePageName(), getModulePageTitle(), getModulePageTemplate());
182        
183        if (page != null)
184        {
185            page.tag("SECTION");
186            page.tag(getModuleTagName());
187            
188            initializeModulePage(page);
189            
190            page.saveChanges();
191            
192            Map<String, Object> eventParams = new HashMap<>();
193            eventParams.put(ObservationConstants.ARGS_PAGE, page);
194            _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_ADDED, _currentUserProvider.getUser(), eventParams));
195        }
196    }
197    
198    @Override
199    public AmetysObjectIterable<Page> getModulePages(Project project, String language)
200    {
201        return _projectManager.getProjectPages(project, getModuleTagName(), language);
202    }
203    
204    
205    /**
206     * Create a new page if not already exists
207     * @param sitemap The sitemap where the page will be created
208     * @param name The page's name
209     * @param pageTitle The page's title as i18nizable text
210     * @param skinTemplate The template from the skin to apply on the page
211     * @return the created page or <code>null</code> if page already exists
212     */
213    protected ModifiablePage _createModulePage(Sitemap sitemap, String name, I18nizableText pageTitle, String skinTemplate)
214    {
215        if (!sitemap.hasChild(name))
216        {
217            ModifiablePage page = sitemap.createChild(name, "ametys:defaultPage");
218            
219            // Title should not be missing, but just in case if the i18n message or the whole catalog does not exists in the requested language
220            // to prevent a non-user-friendly error and still generate the project workspace.
221            page.setTitle(StringUtils.defaultIfEmpty(_i18nUtils.translate(pageTitle, sitemap.getName()), "Missing title"));
222            page.setType(PageType.NODE);
223            page.setSiteName(sitemap.getSiteName());
224            page.setSitemapName(sitemap.getName());
225            
226            Site site = page.getSite();
227            Skin skin = _skinsManager.getSkin(site.getSkinId());
228            SkinTemplate template = skin.getTemplate(skinTemplate);
229            if (template != null)
230            {
231                // Set the type and template.
232                page.setType(PageType.CONTAINER);
233                page.setTemplate(skinTemplate);
234            }
235            else
236            {
237                getLogger().error(String.format(
238                        "The project workspace  '%s' was created with the skin '%s'  which doesn't possess the mandatory template '%s'.\nThe '%s' page of the project workspace could not be initialized.",
239                        site.getName(), site.getSkinId(), skinTemplate, page.getName()));
240            }
241            
242            sitemap.saveChanges();
243            
244            return page;
245        }
246        else
247        {
248            return null;
249        }
250    }
251    
252    /**
253     * Delete all pages of the project sites matching the page name
254     * @param project The project
255     * @param pageName The page name
256     */
257    protected void _deletePages(Project project, String pageName)
258    {
259        for (Site site : project.getSites())
260        {
261            for (Sitemap sitemap : site.getSitemaps())
262            {
263                if (sitemap.hasChild(pageName))
264                {
265                    ModifiablePage page = sitemap.getChild(pageName);
266                    _pageDAO.deletePage(page, true);
267                }
268            }
269            
270            site.saveChanges();
271        }
272    }
273    
274    /**
275     * Get the default value of the XSLT parameter of the given service.
276     * @param serviceId the service ID.
277     * @return the default XSLT parameter value.
278     */
279    protected String _getDefaultXslt(String serviceId)
280    {
281        Service service = _serviceEP.hasExtension(serviceId) ? _serviceEP.getExtension(serviceId) : null;
282        if (service != null)
283        {
284            @SuppressWarnings("unchecked")
285            ElementDefinition<String> xsltParameterDefinition = (ElementDefinition<String>) service.getParameters().get("xslt");
286            
287            if (xsltParameterDefinition != null)
288            {
289                return xsltParameterDefinition.getDefaultValue();
290            }
291        }
292        
293        return StringUtils.EMPTY;
294    }
295    
296    /**
297     * Returns the tag of apply to module page
298     * @return The tag for module page
299     */
300    protected abstract String getModuleTagName();
301    
302    /**
303     * Returns the module page's name
304     * @return The module page's name
305     */
306    protected abstract String getModulePageName();
307    
308    /**
309     * Returns the module page's title as i18n
310     * @return The module page's title
311     */
312    protected abstract I18nizableText getModulePageTitle();
313    
314    /**
315     * Returns the template to use for module's page
316     * @return The template
317     */
318    protected String getModulePageTemplate() 
319    {
320        return ProjectConstants.PROJECT_TEMPLATE;
321    }
322    
323    /**
324     * Initialize the module page
325     * @param modulePage The module page
326     */
327    protected abstract void initializeModulePage(ModifiablePage modulePage);
328    
329    /**
330     * Internal process when module is deactivated
331     * @param project The project
332     */
333    protected void _internalDeactivateModule(Project project) 
334    {
335        // Empty
336    }
337    
338    /**
339     * Internal process when module is activated
340     * @param project The project
341     */
342    protected void _internalActivateModule(Project project)
343    {
344        // Empty
345    }
346    
347    /**
348     * Utility method to get or create an ametys object
349     * @param <A> A sub class of AmetysObject
350     * @param parent The parent object
351     * @param name The ametys object name
352     * @param type The ametys object type
353     * @param create True to create the object if it does not exist
354     * @return ametys object
355     * @throws AmetysRepositoryException if an repository error occurs
356     */
357    protected <A extends AmetysObject> A _getAmetysObject(ModifiableTraversableAmetysObject parent, String name, String type, boolean create) throws AmetysRepositoryException
358    {
359        A object = null;
360        
361        if (parent.hasChild(name))
362        {
363            object = parent.getChild(name);
364        }
365        else if (create)
366        {
367            object = parent.createChild(name, type);
368            parent.saveChanges();
369        }
370        
371        return object;
372    }
373}