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