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