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.util.Optional;
019
020import javax.xml.transform.TransformerConfigurationException;
021import javax.xml.transform.TransformerFactory;
022import javax.xml.transform.TransformerFactoryConfigurationError;
023import javax.xml.transform.dom.DOMResult;
024import javax.xml.transform.sax.SAXTransformerFactory;
025import javax.xml.transform.sax.TransformerHandler;
026
027import org.apache.avalon.framework.configuration.Configuration;
028import org.apache.avalon.framework.configuration.ConfigurationException;
029import org.apache.avalon.framework.configuration.DefaultConfigurationSerializer;
030import org.apache.commons.collections4.MapUtils;
031import org.apache.commons.lang3.LocaleUtils;
032import org.w3c.dom.Document;
033import org.w3c.dom.Element;
034import org.xml.sax.SAXException;
035
036import org.ametys.cms.data.holder.impl.DefaultModifiableModelAwareDataHolder;
037import org.ametys.cms.repository.Content;
038import org.ametys.cms.transformation.Configuration2XMLValuesTransformer;
039import org.ametys.plugins.repository.AmetysRepositoryException;
040import org.ametys.plugins.repository.data.extractor.ModelAwareValuesExtractor;
041import org.ametys.plugins.repository.data.extractor.xml.ModelAwareXMLValuesExtractor;
042import org.ametys.plugins.repository.data.holder.ModelAwareDataHolder;
043import org.ametys.plugins.repository.data.holder.ModelLessDataHolder;
044import org.ametys.plugins.repository.data.holder.ModifiableModelAwareDataHolder;
045import org.ametys.plugins.repository.data.holder.impl.DefaultModelLessDataHolder;
046import org.ametys.plugins.repository.data.repositorydata.ModifiableRepositoryData;
047import org.ametys.plugins.repository.data.repositorydata.RepositoryData;
048import org.ametys.plugins.repository.data.repositorydata.impl.MemoryRepositoryData;
049import org.ametys.runtime.model.Model;
050import org.ametys.runtime.model.View;
051import org.ametys.runtime.model.type.DataContext;
052import org.ametys.web.parameters.view.ViewParametersManager;
053import org.ametys.web.parameters.view.ViewParametersModel;
054import org.ametys.web.repository.page.Zone;
055import org.ametys.web.repository.page.ZoneItem;
056import org.ametys.web.service.Service;
057
058/**
059 * A configurable zone item
060 */
061public class ConfigurableVirtualZoneItem implements ZoneItem
062{
063    /** The AbstractVirtualConfigurablePage */
064    protected AbstractConfigurableVirtualPage _page;
065    /** The zone item name */
066    protected String _name;
067    /** The VirtualPageZoneItemConfiguration */
068    protected VirtualZoneItemConfiguration _configuration;
069    /** The VirtualPageZoneConfiguration */
070    protected VirtualZoneConfiguration _zoneConfiguration;
071    /** The ZoneType */
072    protected ZoneType _zoneType;
073    /** The scheme */
074    private String _scheme;
075    /** The factory */
076    private ConfigurableVirtualZoneItemFactory _factory;
077
078    
079    /**
080     * The constructor of a configurable zone item
081     * @param page The AbstractVirtualConfigurablePage page
082     * @param configuration The virtual page's zone item's configuration
083     * @param scheme The scheme
084     * @param factory The factory
085     */
086    public ConfigurableVirtualZoneItem(AbstractConfigurableVirtualPage page, VirtualZoneItemConfiguration configuration, String scheme, ConfigurableVirtualZoneItemFactory factory)
087    {
088        _configuration = configuration;
089        _zoneConfiguration = _configuration.getParentZoneConfiguration();
090        _page = page;
091        _name = _configuration.getName();
092        _zoneType = configuration.getZoneType();
093        _scheme = scheme;
094        _factory = factory;
095    }
096
097    @Override
098    public String getViewName() throws AmetysRepositoryException
099    {
100        return _configuration.getView();
101    }
102    
103    @Override
104    public String getName() throws AmetysRepositoryException
105    {
106        return _name;
107    }
108
109    @Override
110    public String getPath() throws AmetysRepositoryException
111    {
112        return getParentPath() + "/ametys-internal:zoneItems/" + _configuration.getName();
113    }
114
115    @Override
116    public String getParentPath() throws AmetysRepositoryException
117    {
118        return _page.getPath() + "/ametys-internal:zones/" + _configuration.getParentZoneName();
119    }
120
121    @Override
122    public ZoneType getType() throws AmetysRepositoryException
123    {
124        return _zoneType;
125    }
126
127    @SuppressWarnings("unchecked")
128    @Override
129    public <C extends Content> C getContent() throws AmetysRepositoryException
130    {
131        if (_zoneType.equals(ZoneType.CONTENT))
132        {
133            return (C) _page.getContent();
134        }
135        
136        throw new UnsupportedOperationException("This zoneitem is not a content zoneitem on this virtual page");
137    }
138
139    @Override
140    public String getServiceId() throws AmetysRepositoryException
141    {
142        if (_zoneType.equals(ZoneType.SERVICE))
143        {
144            return _configuration.getServiceId();
145        }
146        
147        throw new UnsupportedOperationException("This zoneitem is not a service zoneitem on this virtual page");
148    }
149
150    @Override
151    public ModelAwareDataHolder getServiceParameters() throws AmetysRepositoryException
152    {
153        if (getType() != ZoneType.SERVICE)
154        {
155            throw new AmetysRepositoryException("Can't get service parameters from a not service zone item");
156        }
157        
158        String serviceId = getServiceId();
159        
160        Service service = _factory.getServiceExtensionPoint().getExtension(serviceId);
161        if (service == null)
162        {
163            throw new IllegalStateException("The virtual page uses unknown service '" + serviceId + "'");
164        }
165        
166        ModifiableRepositoryData repositoryData = new MemoryRepositoryData(SERVICE_PARAMETERS_DATA_NAME);
167        
168        Configuration serviceParamsConf = _configuration.getConfiguration().getChild("parameters", true);
169        return _extractValues(repositoryData, service, serviceParamsConf);
170    }
171    
172    @Override
173    public ModelLessDataHolder getDataHolder()
174    {
175        RepositoryData repositoryData = new MemoryRepositoryData(Zone.ZONEITEM_DATA_NAME);
176        return new DefaultModelLessDataHolder(_factory.getZoneItemDataTypeExtensionPoint(), repositoryData);
177    }
178
179    @Override
180    public String getId() throws AmetysRepositoryException
181    {
182        return _scheme + "://" + _name + "?pageId=" + _page.getId() + "&zoneId=" + _zoneConfiguration.getId();
183    }
184
185    @SuppressWarnings("unchecked")
186    @Override
187    public Zone getParent() throws AmetysRepositoryException
188    {
189        return getZone();
190    }
191    
192    @Override
193    public Zone getZone()
194    {
195        return _factory.getZoneFactory().createZone(_page, _zoneConfiguration.getId());
196    }
197
198    @Override
199    public ModelAwareDataHolder getZoneItemParametersHolder() throws AmetysRepositoryException
200    {
201        // No parameters are available on virtual zone item
202        return null;
203    }
204
205    @Override
206    public ModelAwareDataHolder getContentViewParametersHolder(String contentViewName) throws AmetysRepositoryException
207    {
208        if (getType() != ZoneType.CONTENT)
209        {
210            throw new AmetysRepositoryException("Can't get content view parameters from a not content zone item");
211        }
212        
213        Content content = getContent();
214        
215        ViewParametersManager viewParametersManager = _factory.getViewParametersManager();
216        Optional<ViewParametersModel> contentViewParameters = viewParametersManager.getContentViewParametersModel(_getSkinId(), content, contentViewName);
217        if (contentViewParameters.isEmpty())
218        {
219            contentViewParameters = Optional.of(new ViewParametersModel("content-no-param", new View(), MapUtils.EMPTY_SORTED_MAP));
220        }
221        
222        ModifiableRepositoryData repositoryData = new MemoryRepositoryData(ViewParametersManager.CONTENT_VIEW_PARAMETERS_COMPOSITE_NAME);
223        
224        Configuration viewParamsConf = _configuration.getConfiguration().getChild("view-parameters", true);
225        return _extractValues(repositoryData, contentViewParameters.get(), viewParamsConf);
226    }
227
228    @Override
229    public ModelAwareDataHolder getServiceViewParametersHolder(String serviceViewName) throws AmetysRepositoryException
230    {
231        if (getType() != ZoneType.SERVICE)
232        {
233            throw new AmetysRepositoryException("Can't get service view parameters from a not service zone item");
234        }
235        
236        String serviceId = getServiceId();
237        
238        Service service = _factory.getServiceExtensionPoint().getExtension(serviceId);
239        if (service == null)
240        {
241            throw new IllegalStateException("The virtual page uses unknown service '" + serviceId + "'");
242        }
243        
244        ViewParametersManager viewParametersManager = _factory.getViewParametersManager();
245        Optional<ViewParametersModel> serviceViewParameters = viewParametersManager.getServiceViewParametersModel(_getSkinId(), serviceId, serviceViewName);
246        if (serviceViewParameters.isEmpty())
247        {
248            serviceViewParameters = Optional.of(new ViewParametersModel("service-no-param", new View(), MapUtils.EMPTY_SORTED_MAP));
249        }
250        
251        ModifiableRepositoryData repositoryData = new MemoryRepositoryData(ViewParametersManager.SERVICE_VIEW_PARAMETERS_COMPOSITE_NAME);
252
253        Configuration viewParamsConf = _configuration.getConfiguration().getChild("view-parameters", true);
254        return _extractValues(repositoryData, serviceViewParameters.get(), viewParamsConf);
255    }
256    
257    private String _getSkinId()
258    {
259        return getZone().getSitemapElement().getSite().getSkinId();
260    }
261    
262    private ModifiableModelAwareDataHolder _extractValues(ModifiableRepositoryData repositoryData, Model model, Configuration configuration)
263    {
264        DataContext dataContext = DataContext.newInstance();
265        dataContext.withLocale(LocaleUtils.toLocale(getParent().getSitemapElement().getSitemapName()));
266        
267        ModifiableModelAwareDataHolder dataHolder = new DefaultModifiableModelAwareDataHolder(repositoryData, model);
268        
269        try
270        {
271            // Configuration may requires interpretation before extraction of the value
272            // for example to translate i18n keys
273            Element xmlValues = _interpretConfiguration(configuration, dataContext);
274            
275            // provide the result to XML values extractor
276            ModelAwareValuesExtractor extractor = new ModelAwareXMLValuesExtractor(xmlValues, model);
277            dataHolder.synchronizeValues(extractor.extractValues());
278        }
279        catch (Exception e)
280        {
281            throw new AmetysRepositoryException("An error occured while retrieving parameters for the virtual page '" + _page.getId() + "'", e);
282        }
283        
284        return dataHolder;
285    }
286    
287    private Element _interpretConfiguration(Configuration contentCfg, DataContext dataContext)
288            throws SAXException, ConfigurationException
289    {
290        DOMResult domResult = new DOMResult();
291        
292        try
293        {
294            TransformerHandler th = ((SAXTransformerFactory) TransformerFactory.newInstance()).newTransformerHandler();
295            th.setResult(domResult);
296            
297            Configuration2XMLValuesTransformer handler = new Configuration2XMLValuesTransformer(th, dataContext, _factory.getI18nUtils());
298            new DefaultConfigurationSerializer().serialize(handler, contentCfg);
299            Element values = ((Document) domResult.getNode()).getDocumentElement();
300            return values;
301        }
302        catch (TransformerConfigurationException | TransformerFactoryConfigurationError e)
303        {
304            throw new IllegalStateException("Failed to retrive transformer handler. Impossible to interpret the configuration", e);
305        }
306    }
307}