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