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.io.IOException;
019import java.io.InputStream;
020import java.util.ArrayList;
021import java.util.Collection;
022import java.util.HashMap;
023import java.util.List;
024import java.util.Map;
025
026import org.apache.avalon.framework.configuration.Configuration;
027import org.apache.avalon.framework.configuration.ConfigurationException;
028import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
029import org.apache.avalon.framework.service.ServiceException;
030import org.apache.avalon.framework.service.ServiceManager;
031import org.apache.commons.lang.StringUtils;
032import org.apache.excalibur.source.Source;
033import org.apache.excalibur.source.SourceNotFoundException;
034import org.apache.excalibur.source.SourceResolver;
035import org.xml.sax.SAXException;
036
037import org.ametys.core.model.type.AbstractStringElementType;
038import org.ametys.core.observation.Event;
039import org.ametys.core.observation.ObservationManager;
040import org.ametys.core.user.CurrentUserProvider;
041import org.ametys.core.util.I18nUtils;
042import org.ametys.plugins.repository.data.holder.ModifiableModelAwareDataHolder;
043import org.ametys.plugins.repository.data.holder.group.impl.ModifiableModelAwareRepeaterEntry;
044import org.ametys.plugins.repository.model.RepeaterDefinition;
045import org.ametys.plugins.workflow.support.WorkflowProvider;
046import org.ametys.plugins.workspaces.project.objects.Project;
047import org.ametys.runtime.i18n.I18nizableText;
048import org.ametys.runtime.model.ElementDefinition;
049import org.ametys.runtime.model.ModelItem;
050import org.ametys.web.ObservationConstants;
051import org.ametys.web.repository.page.ModifiablePage;
052import org.ametys.web.repository.page.ModifiableZone;
053import org.ametys.web.repository.page.ModifiableZoneItem;
054import org.ametys.web.repository.page.Page.PageType;
055import org.ametys.web.repository.page.ZoneItem.ZoneType;
056import org.ametys.web.repository.site.Site;
057import org.ametys.web.repository.sitemap.Sitemap;
058import org.ametys.web.service.Service;
059import org.ametys.web.service.ServiceExtensionPoint;
060import org.ametys.web.service.ServiceParameter;
061import org.ametys.web.skin.Skin;
062import org.ametys.web.skin.SkinTemplate;
063import org.ametys.web.skin.SkinTemplateZone;
064import org.ametys.web.skin.SkinsManager;
065
066/**
067 * Initializes all project workspace pages when a sitemap is created.
068 */
069public class InitializeProjectSitemapObserver extends AbstractInitializeProjectObserver
070{
071    /** The i18n utils. */
072    protected I18nUtils _i18nUtils;
073    
074    /** The observation manager. */
075    protected ObservationManager _observationManager;
076    
077    /** The skins manager. */
078    protected SkinsManager _skinsManager;
079    
080    /** Current user provider */
081    protected CurrentUserProvider _currentUserProvider;
082    
083    /** The service extension point. */
084    protected ServiceExtensionPoint _serviceEP;
085    
086    /** The plugin name. */
087    protected String _pluginName;
088    
089    /** The i18n catalogue. */
090    protected String _i18nCatalogue;
091
092    /** The workflow provider */
093    protected WorkflowProvider _workflowProvider;
094
095    /** Source resover */
096    protected SourceResolver _sourceResolver;
097
098    @Override
099    public void setPluginInfo(String pluginName, String featureName, String id)
100    {
101        _pluginName = pluginName;
102        _i18nCatalogue = "plugin." + pluginName;
103    }
104    
105    @Override
106    public void service(ServiceManager manager) throws ServiceException
107    {
108        super.service(manager);
109        _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE);
110        _observationManager = (ObservationManager) manager.lookup(ObservationManager.ROLE);
111        _skinsManager = (SkinsManager) manager.lookup(SkinsManager.ROLE);
112        _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE);
113        _serviceEP = (ServiceExtensionPoint) manager.lookup(ServiceExtensionPoint.ROLE);
114        _workflowProvider = (WorkflowProvider) manager.lookup(WorkflowProvider.ROLE);
115        _sourceResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE);
116    }
117
118    @Override
119    public void doObserve(Event event, Map<String, Object> transientVars, Site site, Project project) throws Exception
120    {
121        if (project != null)
122        {
123            // Initialize each sitemap with a set of predefined and configured pages
124            for (Sitemap sitemap : site.getSitemaps())
125            {
126                if (sitemap.getChildrenPages().getSize() == 0)
127                {
128                    _initializeSitemap(sitemap, project);
129                }
130            }
131        }
132    }
133    
134    
135    /**
136     * Initialize the given sitemap.
137     * @param sitemap the Sitemap object.
138     * @param project the corresponding project
139     */
140    protected void _initializeSitemap(Sitemap sitemap, Project project)
141    {
142        // Home page (index)
143        ModifiablePage indexPage = _createPage(sitemap, "index", new I18nizableText(_i18nCatalogue, "PLUGINS_WORKSPACES_PROJECT_WORKSPACE_PAGE_INDEX_TITLE"));
144        if (indexPage != null)
145        {
146            _initializeIndexPage(project, indexPage);
147        }
148        
149        _projectManager.initializeModulesSitemap(project, sitemap);
150        
151        // Notify of the sitemap change.
152        Map<String, Object> eventParams = new HashMap<>();
153        eventParams.put(ObservationConstants.ARGS_SITEMAP, sitemap);
154        _observationManager.notify(new Event(ObservationConstants.EVENT_SITEMAP_UPDATED, _currentUserProvider.getUser(), eventParams));
155    }
156    
157    /**
158     * Create a new page if not already exists
159     * @param sitemap The sitemap where the page will be created
160     * @param name The page's name
161     * @param i18nTitle The page's title
162     * @return the created page or <code>null</code> if page already exists
163     */
164    protected ModifiablePage _createPage(Sitemap sitemap, String name, I18nizableText i18nTitle)
165    {
166        if (!sitemap.hasChild(name))
167        {
168            ModifiablePage page = sitemap.createChild(name, "ametys:defaultPage");
169            
170            // Title should not be missing, but just in case if the i18n message or the whole catalog does not exists in the requested language
171            // to prevent a non-user-friendly error and still generate the project workspace.
172            page.setTitle(StringUtils.defaultIfEmpty(_i18nUtils.translate(i18nTitle, sitemap.getName()), "Missing title"));
173            page.setType(PageType.NODE);
174            page.setSiteName(sitemap.getSiteName());
175            page.setSitemapName(sitemap.getName());
176            
177            sitemap.saveChanges();
178            
179            return page;
180        }
181        else
182        {
183            return null;
184        }
185    }
186    
187    /**
188     * Initialize the index page.
189     * @param project The project
190     * @param indexPage the index page.
191     */
192    protected void _initializeIndexPage(Project project, ModifiablePage indexPage)
193    {
194        Site site = indexPage.getSite();
195        Skin skin = _skinsManager.getSkin(site.getSkinId());
196        
197        String path = "skin:" + site.getSkinId() + "://conf/project-home-model.xml";
198        Source cfgFile = null;
199        try
200        {
201            cfgFile = _sourceResolver.resolveURI(path);
202            if (!cfgFile.exists())
203            {
204                throw new SourceNotFoundException(cfgFile.getURI() + " does not exist");
205            }
206            
207            _initializeIndexPage(project, skin, indexPage, cfgFile);
208        }
209        catch (IOException e)
210        {
211            getLogger().error("The model file '{}' for project home page does not exists. The '{}' page of the project workspace {} will be initialized with default values", path, indexPage.getPathInSitemap(), project.getName(), e);
212        }
213        finally
214        {
215            _sourceResolver.release(cfgFile);
216        }
217    }
218    
219    /**
220     * Initialize the index page from a configuration file
221     * @param project the project
222     * @param skin the skin
223     * @param indexPage the index page
224     * @param cfgFile the configuration file
225     */
226    protected void _initializeIndexPage(Project project, Skin skin, ModifiablePage indexPage, Source cfgFile)
227    {
228        try (InputStream is = cfgFile.getInputStream())
229        {
230            Configuration configuration = new DefaultConfigurationBuilder().build(is);
231            String templateId = configuration.getAttribute("template", "project-index");
232            
233            SkinTemplate template = skin.getTemplate(templateId);
234            if (template == null)
235            {
236                getLogger().error("The project home page template use an unexsiting template '{}'. The '{}' page of the project workspace '{}' could not be initialized.", templateId, indexPage.getPathInSitemap(), project.getName());
237                return;
238            }
239            
240            // Set the type and template.
241            indexPage.setType(PageType.CONTAINER);
242            indexPage.setTemplate(template.getId());
243            
244            // Initialize the zones.
245            Map<String, SkinTemplateZone> templateZones = template.getZones();
246            
247            for (Configuration zoneConf : configuration.getChildren())
248            {
249                String zoneName = zoneConf.getAttribute("id");
250                if (templateZones.containsKey(zoneName))
251                {
252                    ModifiableZone zone = indexPage.createZone(zoneName);
253                    
254                    for (Configuration serviceConf : zoneConf.getChildren("service"))
255                    {
256                        String serviceId = serviceConf.getAttribute("id");
257                        Configuration paramsConf = serviceConf.getChild("parameters", true);
258                        
259                        Service service = _serviceEP.getExtension(serviceId);
260                        
261                        if (service != null)
262                        {
263                            ModifiableZoneItem zoneItem = zone.addZoneItem();
264                            zoneItem.setType(ZoneType.SERVICE);
265                            zoneItem.setServiceId(serviceId);
266                            
267                            Map<String, ModelItem> serviceModelParams = service.getParameters();
268                            _setServiceParameters(serviceModelParams.values(), zoneItem.getServiceParameters(), paramsConf, indexPage.getSitemapName());
269                        }
270                        else
271                        {
272                            getLogger().error("The project home page template defines an unexsiting service '{}'. The '{}' page of the project workspace {} could not be initialized properly.", serviceId, indexPage.getPathInSitemap(), project.getName());
273                        }
274                    }
275                }
276                else
277                {
278                    getLogger().error("The project home page template defines an unexsiting zone '{}' for template '{}'. The '{}' page of the project workspace {} could not be initialized properly.", zoneName, templateId, indexPage.getPathInSitemap(), project.getName());
279                }
280            }
281            
282            indexPage.saveChanges();
283            
284            Map<String, Object> eventParams = new HashMap<>();
285            eventParams.put(ObservationConstants.ARGS_PAGE, indexPage);
286            _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_ADDED, _currentUserProvider.getUser(), eventParams));
287        }
288        catch (ConfigurationException | SAXException | IOException e)
289        {
290            getLogger().error("Fail to read the project home page model '{}'. The '{}' page of the project workspace {} could not be initialized", cfgFile.getURI(), indexPage.getPathInSitemap(), project.getName(), e);
291        }
292    }
293    
294    private void _setServiceParameters (Collection<ModelItem> modelItems, ModifiableModelAwareDataHolder dataHolder, Configuration paramsConf, String lang) throws ConfigurationException
295    {
296        for (ModelItem modelItem : modelItems)
297        {
298            String paramName = modelItem.getName();
299            
300            if (modelItem instanceof ElementDefinition)
301            {
302                Configuration paramConf = paramsConf.getChild(paramName, false);
303                if (paramConf != null)
304                {
305                    if (((ElementDefinition) modelItem).isMultiple())
306                    {
307                        // FIXME we need a XML service descriptor, for now only multiple values of type string are supported
308                        if (((ElementDefinition) modelItem).getType() instanceof AbstractStringElementType)
309                        {
310                            List<String> typedValues = new ArrayList<>();
311                            
312                            Configuration[] valuesConf = paramConf.getChildren("value");
313                            for (Configuration valueConf : valuesConf)
314                            {
315                                typedValues.add(valueConf.getValue());
316                            }
317                            
318                            dataHolder.setValue(paramName, typedValues.toArray(new String[typedValues.size()]));
319                        }
320                        else
321                        {
322                            getLogger().warn("Only string multiple values are supported in the XML configuration of a service. Can not evaluate the service parameter " + modelItem.getPath());
323                        }
324                        
325                    }
326                    else
327                    {
328                        String paramValue = paramConf.getValue("");
329                        boolean i18n = paramConf.getAttributeAsBoolean("i18n", false);
330                        if (i18n)
331                        {
332                            I18nizableText i18nText = I18nizableText.parseI18nizableText(paramConf, "application");
333                            paramValue = _i18nUtils.translate(i18nText, lang);
334                        }
335                        
336                        Object typedValue = ((ElementDefinition) modelItem).getType().castValue(paramValue);
337                        dataHolder.setValue(paramName, typedValue);
338                    }
339                    
340                    
341                }
342                else if (((ElementDefinition) modelItem).getDefaultValue() != null)
343                {
344                    // set default value
345                    dataHolder.setValue(paramName, ((ServiceParameter) modelItem).getDefaultValue());
346                }
347            }
348            else if (modelItem instanceof RepeaterDefinition)
349            {
350                Configuration[] entriesConf = paramsConf.getChild(paramName, true).getChildren("entry");
351                
352                List<ModelItem> subModelItems = ((RepeaterDefinition) modelItem).getChildren();
353                
354                int entryCount = 1;
355                for (Configuration entryConf : entriesConf)
356                {
357                    ModifiableModelAwareRepeaterEntry entry = dataHolder.getRepeater(paramName, true).addEntry(entryCount);
358                    _setServiceParameters(subModelItems, entry, entryConf, lang);
359                    entryCount++;
360                }
361            }
362        }
363        
364    }
365}