001/*
002 *  Copyright 2023 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.web.repository.page.virtual;
017
018import java.io.InputStream;
019import java.util.Collection;
020import java.util.LinkedHashMap;
021import java.util.Map;
022
023import org.apache.avalon.framework.configuration.Configurable;
024import org.apache.avalon.framework.configuration.Configuration;
025import org.apache.avalon.framework.configuration.ConfigurationException;
026import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
027import org.apache.avalon.framework.context.Context;
028import org.apache.avalon.framework.context.ContextException;
029import org.apache.avalon.framework.context.Contextualizable;
030import org.apache.avalon.framework.service.ServiceException;
031import org.apache.avalon.framework.service.ServiceManager;
032import org.apache.avalon.framework.service.Serviceable;
033import org.apache.cocoon.components.ContextHelper;
034import org.apache.cocoon.environment.Request;
035import org.apache.excalibur.source.Source;
036
037import org.ametys.core.util.filereloader.FileReloader;
038import org.ametys.core.util.filereloader.FileReloaderUtils;
039import org.ametys.runtime.plugin.component.AbstractLogEnabled;
040import org.ametys.runtime.plugin.component.PluginAware;
041import org.ametys.web.WebConstants;
042import org.ametys.web.repository.page.Page;
043import org.ametys.web.repository.site.Site;
044import org.ametys.web.repository.site.SiteManager;
045
046/**
047 * This class represents the configuration of a virtual page based on an XML configuration
048 */
049public class VirtualPageConfiguration extends AbstractLogEnabled implements Configurable, Serviceable, Contextualizable, PluginAware
050{
051    /** The file reloader utils */
052    protected FileReloaderUtils _fileReloaderUtils;
053    /** The site manager */
054    protected SiteManager _siteManager;
055    /** The context */
056    protected Context _context;
057    
058    private String _id;
059    private String _template;
060    private Map<String, VirtualZoneConfiguration> _zonesConfiguration;
061    private String _path;
062
063    @Override
064    public void service(ServiceManager manager) throws ServiceException 
065    {
066        _fileReloaderUtils = (FileReloaderUtils) manager.lookup(FileReloaderUtils.ROLE);
067        _siteManager = (SiteManager) manager.lookup(SiteManager.ROLE);
068    }
069    
070    @Override
071    public void contextualize(Context context) throws ContextException
072    {
073        _context = context;
074    }
075    
076    public void setPluginInfo(String pluginName, String featureName, String id)
077    {
078        _id = id;
079    }
080    
081    public void configure(Configuration configuration) throws ConfigurationException
082    {
083        _path = configuration.getChild("path").getValue();
084    }
085    
086    private void _configure(Configuration configuration) throws ConfigurationException 
087    {
088        _template = configuration.getAttribute("template", "page");
089        
090        _zonesConfiguration = new LinkedHashMap<>();
091        Configuration[] zonesConfigurations = configuration.getChildren("zone");
092        for (Configuration zoneConfiguration : zonesConfigurations) 
093        {
094            VirtualZoneConfiguration zone = new VirtualZoneConfiguration(zoneConfiguration);
095            
096            _zonesConfiguration.put(zone.getId(), zone);
097        }
098    }
099    
100    /**
101     * Get the path to override the configuration with a skin configuration
102     * @return The path
103     */
104    public String getPath()
105    {
106        return _path;
107    }
108    
109    /**
110     * Get the id
111     * @return The id
112     */
113    public String getId() 
114    {
115        return _id;
116    }
117    
118    /**
119     * Get the template's id
120     * @param rootPage The root page
121     * @return The template's id
122     */
123    public String getTemplate(Page rootPage) 
124    {
125        loadSkinConfigurationIfExists(rootPage);
126        return _template;
127    }
128    
129    /**
130     * Get the zones configurations
131     * @param rootPage The root page
132     * @return The collection of VirtualPageZoneConfiguration
133     */
134    public Collection<VirtualZoneConfiguration> getZonesConfigurations(Page rootPage) 
135    {
136        loadSkinConfigurationIfExists(rootPage);
137        return _zonesConfiguration.values();
138    }
139    
140    /**
141     * Check if the virtual page has a zone, by its id
142     * @param id The zone id
143     * @param rootPage The root page
144     * @return true if the page has a zone of the id given, false otherwise
145     */
146    public boolean hasZoneConfiguration(String id, Page rootPage) 
147    {
148        loadSkinConfigurationIfExists(rootPage);
149        return _zonesConfiguration.containsKey(id);
150    }
151    
152    /**
153     * Get the zone configuration for a zone id
154     * @param id The zone id
155     * @param rootPage The root page
156     * @return The VirtualPageZoneConfiguration, null if none is found
157     */
158    public VirtualZoneConfiguration getZoneConfiguration(String id, Page rootPage) 
159    {
160        loadSkinConfigurationIfExists(rootPage);
161        return _zonesConfiguration.get(id);
162    }
163    
164    /**
165     * Load the configuration from skin if one is found
166     * @param rootPage The root page
167     */
168    private void loadSkinConfigurationIfExists(Page rootPage) 
169    {
170        Site site = rootPage.getSite();
171        String siteName = site.getName();
172        String skinId = site.getSkinId();
173        
174        Request request = ContextHelper.getRequest(_context);
175        Site oldSite = (Site) request.getAttribute(WebConstants.REQUEST_ATTR_SITE);
176        String oldSiteName = (String) request.getAttribute(WebConstants.REQUEST_ATTR_SITE_NAME);
177        String oldSkinId = (String) request.getAttribute(WebConstants.REQUEST_ATTR_SKIN_ID);
178
179        try
180        {
181            request.setAttribute(WebConstants.REQUEST_ATTR_SITE, site);
182            request.setAttribute(WebConstants.REQUEST_ATTR_SITE_NAME, siteName);
183            request.setAttribute(WebConstants.REQUEST_ATTR_SKIN_ID, skinId);
184            
185            String filePath = getPath();
186            try 
187            {
188                _fileReloaderUtils.updateFile(filePath, true, new VirtualPageConfigurationFileReloader(skinId, filePath, this));
189            }
190            catch (Exception e) 
191            {
192                throw new IllegalStateException("No configurations found at path '" + filePath + "' in the skin for virtual page of id " + _id, e);
193            }
194        }
195        finally
196        {
197            request.setAttribute(WebConstants.REQUEST_ATTR_SITE, oldSite);
198            request.setAttribute(WebConstants.REQUEST_ATTR_SITE_NAME, oldSiteName);
199            request.setAttribute(WebConstants.REQUEST_ATTR_SKIN_ID, oldSkinId);
200        }
201    }
202
203    /**
204     * Class representing a virtual page conf file reloader
205     */
206    public static class VirtualPageConfigurationFileReloader implements FileReloader
207    {
208        private String _skinId;
209        private String _filePath;
210        private VirtualPageConfiguration _component;
211     
212        /**
213         * Constructor
214         * @param skinId the skin id
215         * @param filePath the file path
216         * @param component the abstract parent component
217         */
218        public VirtualPageConfigurationFileReloader (String skinId, String filePath, VirtualPageConfiguration component)
219        {
220            _skinId = skinId;
221            _filePath = filePath;
222            _component = component;
223        }
224        
225        /**
226         * Get the skinId
227         * @return the skinId
228         */
229        public String getSkinId()
230        {
231            return _skinId;
232        }
233        
234        /**
235         * Get the file path
236         * @return the file path
237         */
238        public String getFilePath()
239        {
240            return _filePath;
241        }
242        
243        /**
244         * Get the parent component
245         * @return the parent component
246         */
247        public VirtualPageConfiguration getComponent()
248        {
249            return _component;
250        }
251        
252        @Override
253        public void updateFile(String sourceUrl, Source source, InputStream is) throws Exception
254        {
255            if (is != null)
256            {
257                Configuration cfg = new DefaultConfigurationBuilder().build(is, source.getURI());
258                getComponent()._configure(cfg);
259            }
260            else
261            {
262                throw new UnsupportedOperationException("Components extending AbstractVirtualPageConfiguration should always have a default configuration file");
263            }
264        }
265        
266        @Override
267        public String getId(String sourceUrl)
268        {
269            return VirtualPageConfigurationFileReloader.class.getName() + "#" + getFilePath() + "#" + getSkinId();
270        }
271    }
272}