001/*
002 *  Copyright 2016 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.project.observers;
017
018import java.util.HashMap;
019import java.util.List;
020import java.util.Map;
021
022import org.apache.avalon.framework.logger.AbstractLogEnabled;
023import org.apache.avalon.framework.service.ServiceException;
024import org.apache.avalon.framework.service.ServiceManager;
025import org.apache.avalon.framework.service.Serviceable;
026import org.apache.commons.lang.StringUtils;
027
028import org.ametys.core.observation.Event;
029import org.ametys.core.observation.ObservationManager;
030import org.ametys.core.observation.Observer;
031import org.ametys.core.user.CurrentUserProvider;
032import org.ametys.core.util.I18nUtils;
033import org.ametys.plugins.repository.metadata.ModifiableCompositeMetadata;
034import org.ametys.plugins.workflow.support.WorkflowProvider;
035import org.ametys.plugins.workspaces.project.ProjectConstants;
036import org.ametys.plugins.workspaces.project.ProjectManager;
037import org.ametys.plugins.workspaces.project.objects.Project;
038import org.ametys.runtime.i18n.I18nizableText;
039import org.ametys.runtime.plugin.component.PluginAware;
040import org.ametys.web.ObservationConstants;
041import org.ametys.web.repository.page.ModifiablePage;
042import org.ametys.web.repository.page.ModifiableZone;
043import org.ametys.web.repository.page.ModifiableZoneItem;
044import org.ametys.web.repository.page.Page.PageType;
045import org.ametys.web.repository.page.ZoneItem.ZoneType;
046import org.ametys.web.repository.site.Site;
047import org.ametys.web.repository.sitemap.Sitemap;
048import org.ametys.web.service.Service;
049import org.ametys.web.service.ServiceExtensionPoint;
050import org.ametys.web.service.ServiceParameter;
051import org.ametys.web.skin.Skin;
052import org.ametys.web.skin.SkinTemplate;
053import org.ametys.web.skin.SkinTemplateZone;
054import org.ametys.web.skin.SkinsManager;
055
056/**
057 * Initializes all project workspace pages when a sitemap is created.
058 */
059public class InitializeProjectSitemapObserver extends AbstractLogEnabled implements Observer, Serviceable, PluginAware
060{
061    /** Workspaces project manager */
062    protected ProjectManager _projectManager;
063    
064    /** The i18n utils. */
065    protected I18nUtils _i18nUtils;
066    
067    /** The observation manager. */
068    protected ObservationManager _observationManager;
069    
070    /** The skins manager. */
071    protected SkinsManager _skinsManager;
072    
073    /** Current user provider */
074    protected CurrentUserProvider _currentUserProvider;
075    
076    /** The service extension point. */
077    protected ServiceExtensionPoint _serviceEP;
078    
079    /** The plugin name. */
080    protected String _pluginName;
081    
082    /** The i18n catalogue. */
083    protected String _i18nCatalogue;
084
085    /** The workflow provider */
086    protected WorkflowProvider _workflowProvider;
087    
088    @Override
089    public void setPluginInfo(String pluginName, String featureName, String id)
090    {
091        _pluginName = pluginName;
092        _i18nCatalogue = "plugin." + pluginName;
093    }
094    
095    @Override
096    public void service(ServiceManager manager) throws ServiceException
097    {
098        _projectManager = (ProjectManager) manager.lookup(ProjectManager.ROLE);
099        _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE);
100        _observationManager = (ObservationManager) manager.lookup(ObservationManager.ROLE);
101        _skinsManager = (SkinsManager) manager.lookup(SkinsManager.ROLE);
102        _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE);
103        _serviceEP = (ServiceExtensionPoint) manager.lookup(ServiceExtensionPoint.ROLE);
104        _workflowProvider = (WorkflowProvider) manager.lookup(WorkflowProvider.ROLE);
105    }
106    
107    @Override
108    public boolean supports(Event event)
109    {
110        // Listen on site updated because it is too early to listen on sitemap added (we need the site be configured and synchronized in live)
111        return event.getId().equals(ObservationConstants.EVENT_SITE_UPDATED);
112    }
113
114    @Override
115    public int getPriority(Event event)
116    {
117        // Will be processed after live synchronization observers
118        return MAX_PRIORITY + 2000;
119    }
120
121    @Override
122    public void observe(Event event, Map<String, Object> transientVars) throws Exception
123    {
124        Site site = (Site) event.getArguments().get(ObservationConstants.ARGS_SITE);
125        if (site != null)
126        {
127            List<Project> projects = _projectManager.getProjectsForSite(site);
128            if (!projects.isEmpty())
129            {
130                Project project = projects.get(0);
131                
132                // Initialize each sitemap with a set of predefined and configured pages
133                for (Sitemap sitemap : site.getSitemaps())
134                {
135                    _initializeSitemap(sitemap, project);
136                }
137            }
138        }
139    }
140    
141    /**
142     * Initialize the given sitemap.
143     * @param sitemap the Sitemap object.
144     * @param project the corresponding project
145     */
146    protected void _initializeSitemap(Sitemap sitemap, Project project)
147    {
148        // Home page (index)
149        ModifiablePage indexPage = _createPage(sitemap, "index", new I18nizableText(_i18nCatalogue, "PLUGINS_WORKSPACES_PROJECT_WORKSPACE_PAGE_INDEX_TITLE"));
150        if (indexPage != null)
151        {
152            _initializeIndexPage(indexPage);
153        }
154        
155        _projectManager.initializeModulesSitemap(project, sitemap);
156        
157        // Notify of the sitemap change.
158        Map<String, Object> eventParams = new HashMap<>();
159        eventParams.put(ObservationConstants.ARGS_SITEMAP, sitemap);
160        _observationManager.notify(new Event(ObservationConstants.EVENT_SITEMAP_UPDATED, _currentUserProvider.getUser(), eventParams));
161    }
162    
163    /**
164     * Create a new page if not already exists
165     * @param sitemap The sitemap where the page will be created
166     * @param name The page's name
167     * @param i18nTitle The page's title
168     * @return the created page or <code>null</code> if page already exists
169     */
170    protected ModifiablePage _createPage(Sitemap sitemap, String name, I18nizableText i18nTitle)
171    {
172        if (!sitemap.hasChild(name))
173        {
174            ModifiablePage page = sitemap.createChild(name, "ametys:defaultPage");
175            
176            // Title should not be missing, but just in case if the i18n message or the whole catalog does not exists in the requested language
177            // to prevent a non-user-friendly error and still generate the project workspace.
178            page.setTitle(StringUtils.defaultIfEmpty(_i18nUtils.translate(i18nTitle, sitemap.getName()), "Missing title"));
179            page.setType(PageType.NODE);
180            page.setSiteName(sitemap.getSiteName());
181            page.setSitemapName(sitemap.getName());
182            
183            sitemap.saveChanges();
184            
185            return page;
186        }
187        else
188        {
189            return null;
190        }
191    }
192    
193    /**
194     * Initialize the index page.
195     * @param indexPage the index page.
196     */
197    protected void _initializeIndexPage(ModifiablePage indexPage)
198    {
199        Site site = indexPage.getSite();
200        Skin skin = _skinsManager.getSkin(site.getSkinId());
201        
202        SkinTemplate template = skin.getTemplate("index");
203        
204        // Check the template index contains the required zones
205        if (template == null || !template.getZones().containsKey("bottom-left") || !template.getZones().containsKey("bottom-right"))
206        {
207            // switch to template 'project' if exists with the required zones
208            SkinTemplate projectTpl = skin.getTemplate(ProjectConstants.PROJECT_TEMPLATE);
209            if (projectTpl != null && projectTpl.getZones().containsKey("bottom-left") && projectTpl.getZones().containsKey("bottom-right"))
210            {
211                template = projectTpl;
212            }
213        }
214        
215        if (template != null)
216        {
217            // Set the type and template.
218            indexPage.setType(PageType.CONTAINER);
219            indexPage.setTemplate("index");
220            
221            // Initialize the zones.
222            Map<String, SkinTemplateZone> templateZones = template.getZones();
223            if (templateZones.containsKey("default"))
224            {
225                _initializeIndexDefaultZone(indexPage);
226            }
227            else
228            {
229                getLogger().error("A 'default' zone is mandatory for template " + template.getId() + " to be used for project's home page");
230                return;
231            }
232            
233            _initializeIndexBottomLeftZone(indexPage, templateZones.containsKey("bottom-left") ? "bottom-left" : "default");
234            _initializeIndexBottomRightZone(indexPage, templateZones.containsKey("bottom-right") ? "bottom-right" : "default");
235            
236            // Do not tag index page as a section.
237            Map<String, Object> eventParams = new HashMap<>();
238            eventParams.put(ObservationConstants.ARGS_PAGE, indexPage);
239            _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_ADDED, _currentUserProvider.getUser(), eventParams));
240        }
241        else
242        {
243            String errorMsg = String.format(
244                    "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.",
245                    site.getName(), site.getSkinId(), ProjectConstants.PROJECT_TEMPLATE, indexPage.getName());
246            
247            getLogger().error(errorMsg);
248        }
249    }
250    
251    /**
252     * Initialize the default zone for the index page
253     * @param indexPage The index page
254     */
255    protected void _initializeIndexDefaultZone(ModifiablePage indexPage)
256    {
257        @SuppressWarnings("unused")
258        ModifiableZone defaultZone = indexPage.createZone("default");
259        
260        // TODO add project presentation zone
261        // -> editable zone containing a pre-filled content 
262    }
263    
264    /**
265     * Initialize the bottom left zone for the index page
266     * @param indexPage the index page
267     * @param zoneName The zone name where to insert the service
268     */
269    protected void _initializeIndexBottomLeftZone(ModifiablePage indexPage, String zoneName)
270    {
271        ModifiableZone zone = indexPage.createZone(zoneName);
272        
273        String serviceId = "org.ametys.plugins.workspaces.module.Members";
274        ModifiableZoneItem defaultZoneItem = zone.addZoneItem();
275        defaultZoneItem.setType(ZoneType.SERVICE);
276        defaultZoneItem.setServiceId(serviceId);
277        
278        ModifiableCompositeMetadata serviceMetadata = defaultZoneItem.getServiceParameters();
279        I18nizableText i18nTitle = new I18nizableText(_i18nCatalogue, "PLUGINS_WORKSPACES_PROJECT_WORKSPACE_PAGE_INDEX_MODULE_USERS_TITLE");
280        serviceMetadata.setMetadata("title", StringUtils.defaultIfEmpty(_i18nUtils.translate(i18nTitle, indexPage.getSitemapName()), "Missing title"));
281        serviceMetadata.setMetadata("results-per-page", 5);
282        serviceMetadata.setMetadata("xslt", _getDefaultXslt(serviceId));
283    }
284    
285    /**
286     * Initialize the bottom right zone for the index page
287     * @param indexPage the index page
288     * @param zoneName The zone name where to insert the service
289     */
290    protected void _initializeIndexBottomRightZone(ModifiablePage indexPage, String zoneName)
291    {
292        ModifiableZone zone = indexPage.createZone(zoneName);
293        
294        String serviceId = "org.ametys.plugins.workspaces.service.ActivityStream";
295        ModifiableZoneItem defaultZoneItem = zone.addZoneItem();
296        defaultZoneItem.setType(ZoneType.SERVICE);
297        defaultZoneItem.setServiceId(serviceId);
298        
299        ModifiableCompositeMetadata serviceMetadata = defaultZoneItem.getServiceParameters();
300        I18nizableText i18nTitle = new I18nizableText(_i18nCatalogue, "PLUGINS_WORKSPACES_PROJECT_WORKSPACE_PAGE_INDEX_ACTIVITY_STREAM_TITLE");
301        serviceMetadata.setMetadata("title", StringUtils.defaultIfEmpty(_i18nUtils.translate(i18nTitle, indexPage.getSitemapName()), "Missing title"));
302        serviceMetadata.setMetadata("activities-per-page", 5);
303        serviceMetadata.setMetadata("max-activities", 20);
304        serviceMetadata.setMetadata("chronological-order", false);
305        serviceMetadata.setMetadata("xslt", _getDefaultXslt(serviceId));
306    }
307    
308    private String _getDefaultXslt(String serviceId)
309    {
310        Service service = _serviceEP.hasExtension(serviceId) ? _serviceEP.getExtension(serviceId) : null;
311        if (service != null)
312        {
313            ServiceParameter xsltParam = (ServiceParameter) service.getParameters().get("xslt");
314            
315            if (xsltParam != null)
316            {
317                return xsltParam.getDefaultValue().toString();
318            }
319        }
320        
321        return StringUtils.EMPTY;
322    }
323}