001/*
002 *  Copyright 2010 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.jcr;
017
018import java.util.List;
019import java.util.Optional;
020
021import javax.jcr.Node;
022import javax.jcr.RepositoryException;
023
024import org.apache.commons.collections4.MapUtils;
025import org.slf4j.Logger;
026import org.slf4j.LoggerFactory;
027import org.xml.sax.ContentHandler;
028import org.xml.sax.SAXException;
029
030import org.ametys.cms.repository.Content;
031import org.ametys.cms.repository.DefaultContent;
032import org.ametys.plugins.repository.AmetysObject;
033import org.ametys.plugins.repository.AmetysRepositoryException;
034import org.ametys.plugins.repository.CopiableAmetysObject;
035import org.ametys.plugins.repository.ModifiableTraversableAmetysObject;
036import org.ametys.plugins.repository.RepositoryConstants;
037import org.ametys.plugins.repository.RepositoryIntegrityViolationException;
038import org.ametys.plugins.repository.data.holder.ModifiableModelAwareDataHolder;
039import org.ametys.plugins.repository.data.holder.ModifiableModelLessDataHolder;
040import org.ametys.plugins.repository.data.holder.impl.DefaultModifiableModelAwareDataHolder;
041import org.ametys.plugins.repository.data.holder.impl.DefaultModifiableModelLessDataHolder;
042import org.ametys.plugins.repository.data.repositorydata.ModifiableRepositoryData;
043import org.ametys.plugins.repository.data.repositorydata.impl.JCRRepositoryData;
044import org.ametys.plugins.repository.jcr.JCRAmetysObject;
045import org.ametys.plugins.repository.jcr.SimpleAmetysObject;
046import org.ametys.plugins.repository.metadata.ModifiableCompositeMetadata;
047import org.ametys.runtime.model.View;
048import org.ametys.runtime.model.exception.NotUniqueTypeException;
049import org.ametys.runtime.model.exception.UnknownTypeException;
050import org.ametys.runtime.model.type.DataContext;
051import org.ametys.web.parameters.view.ViewParametersManager;
052import org.ametys.web.parameters.view.ViewParametersModel;
053import org.ametys.web.repository.content.SharedContent;
054import org.ametys.web.repository.content.WebContent;
055import org.ametys.web.repository.page.ModifiableZone;
056import org.ametys.web.repository.page.ModifiableZoneItem;
057import org.ametys.web.repository.page.Page;
058import org.ametys.web.repository.page.Zone;
059import org.ametys.web.service.Service;
060
061/**
062 *  implementation of a {@link ModifiableZoneItem}.
063 */
064public class DefaultZoneItem extends SimpleAmetysObject<DefaultZoneItemFactory> implements ModifiableZoneItem, CopiableAmetysObject
065{
066    /** Constant for title metadata. */
067    public static final String METADATA_TYPE = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":type";
068    /** Constant for service metadata. */
069    public static final String METADATA_SERVICE = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":service";
070    /** Constant for content metadata. */
071    public static final String METADATA_CONTENT = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":content";
072    /** Constant service parameters node type. */
073    public static final String SERVICE_PARAM_NODETYPE = RepositoryConstants.NAMESPACE_PREFIX + ":compositeMetadata";
074    
075    /**
076     * Constant for "metadata set name" metadata.
077     * @deprecated Use {@link #METADATA_VIEW_NAME} instead
078     */
079    @Deprecated
080    public static final String METADATA_METADATASET_NAME = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":metadataSetName";
081    
082    /**
083     * Constant for view name metadata.
084     * TODO NEWATTRIBUTEAPI_SERVICE: Change the name of this metadata to viewName (there may be a lot of impacts..)
085     */
086    public static final String METADATA_VIEW_NAME = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":metadataSetName";
087    
088    private static final Logger __LOGGER = LoggerFactory.getLogger(DefaultZoneItem.class);
089
090    /**
091     * Creates a {@link DefaultZoneItem}.
092     * 
093     * @param node the node backing this {@link AmetysObject}.
094     * @param parentPath the parent path in the Ametys hierarchy.
095     * @param factory the {@link DefaultZoneItemFactory} which creates the AmetysObject.
096     */
097    public DefaultZoneItem(Node node, String parentPath, DefaultZoneItemFactory factory)
098    {
099        super(node, parentPath, factory);
100    }
101    
102    public ZoneType getType() throws AmetysRepositoryException
103    {
104        try
105        {
106            return ZoneType.valueOf(getNode().getProperty(METADATA_TYPE).getString());
107        }
108        catch (RepositoryException e)
109        {
110            throw new AmetysRepositoryException("Unable to get type property", e);
111        }
112    }
113
114    public void setType(ZoneType type) throws AmetysRepositoryException
115    {
116        try
117        {
118            getNode().setProperty(METADATA_TYPE, type.name());
119        }
120        catch (RepositoryException e)
121        {
122            throw new AmetysRepositoryException("Unable to set type property", e);
123        }
124    }
125
126    public <C extends Content> C getContent() throws AmetysRepositoryException
127    {
128        try
129        {
130            return _getFactory().<C>resolveAmetysObject(getNode().getProperty(METADATA_CONTENT).getNode());
131        }
132        catch (RepositoryException e)
133        {
134            throw new AmetysRepositoryException("Unable to get content property", e);
135        }
136    }
137
138    public <C extends Content> void setContent(C content) throws AmetysRepositoryException
139    {
140        try
141        {
142            // FIXME stop using getNode that is JCR but use AmetysObject API... if it seems cool to you
143            getNode().setProperty(METADATA_CONTENT, ((JCRAmetysObject) content).getNode());
144        }
145        catch (RepositoryException e)
146        {
147            throw new AmetysRepositoryException("Unable to set content property", e);
148        }    
149    }
150    
151    public String getViewName() throws AmetysRepositoryException
152    {
153        try
154        {
155            if (getNode().hasProperty(METADATA_VIEW_NAME))
156            {
157                return getNode().getProperty(METADATA_VIEW_NAME).getString();
158            }
159            return null;
160        }
161        catch (RepositoryException e)
162        {
163            throw new AmetysRepositoryException("Unable to get view name property.", e);
164        }
165    }
166    
167    @Override
168    public void setViewName(String viewName) throws AmetysRepositoryException
169    {
170        try
171        {
172            getNode().setProperty(METADATA_VIEW_NAME, viewName);
173        }
174        catch (RepositoryException e)
175        {
176            throw new AmetysRepositoryException("Unable to set view name property.", e);
177        }
178    }
179    
180    @Override
181    public String getServiceId() throws AmetysRepositoryException
182    {
183        try
184        {
185            return getNode().getProperty(METADATA_SERVICE).getString();
186        }
187        catch (RepositoryException e)
188        {
189            throw new AmetysRepositoryException("Unable to get service property", e);
190        }
191    }
192    
193    @Override
194    public void setServiceId(String serviceId) throws AmetysRepositoryException
195    {
196        try
197        {
198            getNode().setProperty(METADATA_SERVICE, serviceId);
199        }
200        catch (RepositoryException e)
201        {
202            throw new AmetysRepositoryException("Unable to set service property", e);
203        }
204    }
205    
206    /**
207     * {@inheritDoc}
208     * @throws IllegalStateException if the service referenced by this zone item does not exist
209     */
210    public ModifiableModelAwareDataHolder getServiceParameters() throws AmetysRepositoryException, IllegalStateException
211    {
212        String serviceId = getServiceId();
213        Service service = _getFactory().getService(serviceId);
214        if (service != null)
215        {
216            ModifiableRepositoryData repositoryData = _getServiceParametersRepositoryData();
217            return new DefaultModifiableModelAwareDataHolder(repositoryData, service);
218        }
219        else
220        {
221            throw new IllegalStateException("The service '" + serviceId + "' referenced by the zone item '" + getId() + "' (" + getPath() + ") does not exist");
222        }
223    }
224    
225    /**
226     * Retrieves the {@link ModifiableRepositoryData} containing the service parameters of this zone item
227     * @return the {@link ModifiableRepositoryData} containing the service parameters of this zone item
228     */
229    protected ModifiableRepositoryData _getServiceParametersRepositoryData()
230    {
231        JCRRepositoryData zoneItemRepositoryData = new JCRRepositoryData(getNode());
232        return zoneItemRepositoryData.hasValue(SERVICE_PARAMETERS_DATA_NAME) ? zoneItemRepositoryData.getRepositoryData(SERVICE_PARAMETERS_DATA_NAME) : zoneItemRepositoryData.addRepositoryData(SERVICE_PARAMETERS_DATA_NAME, SERVICE_PARAM_NODETYPE);
233    }
234    
235    public ModifiableModelAwareDataHolder getZoneItemParametersHolder() throws AmetysRepositoryException
236    {
237        JCRRepositoryData zoneItemRepositoryData = new JCRRepositoryData(getNode());
238        
239        ViewParametersManager viewParametersManager = _getFactory().getViewParametersManager();
240        Optional<ViewParametersModel> zoneItemViewParameters = viewParametersManager.getZoneItemViewParametersModel(this);
241        if (zoneItemViewParameters.isEmpty())
242        {
243            zoneItemViewParameters = Optional.of(new ViewParametersModel("zoneitem-no-param", new View(), MapUtils.EMPTY_SORTED_MAP));
244        }
245        return viewParametersManager.getParametersHolder(zoneItemRepositoryData, zoneItemViewParameters.get());
246    }
247    
248    public ModifiableModelLessDataHolder getDataHolder()
249    {
250        ModifiableRepositoryData repositoryData = new JCRRepositoryData(getNode());
251        return new DefaultModifiableModelLessDataHolder(_getFactory().getPageDataTypeExtensionPoint(), repositoryData);
252    }
253    
254    /*
255     * The default implementation is overridden here to not use the getDataHolder()#dataToSAX implementation
256     * Each #dataToSAX method calls the next one and the implementation that has to change is the last one.
257     * If the getDataHolder() is used for one, the next ones will be called with the default implementation
258     */
259    public void dataToSAX(ContentHandler contentHandler, DataContext context) throws SAXException, UnknownTypeException, NotUniqueTypeException
260    {
261        for (String name : getDataNames())
262        {
263            dataToSAX(contentHandler, name, context);
264        }
265    }
266    
267    public void dataToSAX(ContentHandler contentHandler, String dataPath, DataContext context) throws SAXException
268    {
269        if (!SERVICE_PARAMETERS_DATA_NAME.equals(dataPath)
270            && !ViewParametersManager.CONTENT_VIEW_PARAMETERS_COMPOSITE_NAME.equals(dataPath)
271            && !ViewParametersManager.SERVICE_VIEW_PARAMETERS_COMPOSITE_NAME.equals(dataPath)
272            && !ViewParametersManager.VIEW_PARAMETERS_COMPOSITE_NAME.equals(dataPath))
273        {
274            ModifiableZoneItem.super.dataToSAX(contentHandler, dataPath, context);
275        }
276    }
277    
278    @Override
279    public ModifiableCompositeMetadata getMetadataHolder()
280    {
281        __LOGGER.warn("The getMetadataHolder method of DefaultZoneItem is deprecated. Use the getDataHolder instead. StackTrace: ", new Exception());
282        return super.getMetadataHolder();
283    }
284    
285    @Override
286    public ModifiableZoneItem copyTo(ModifiableTraversableAmetysObject parent, String name, List<String> restrictTo) throws AmetysRepositoryException
287    {
288        return copyTo(parent, name);
289    }
290    
291    @Override
292    public ModifiableZoneItem copyTo(ModifiableTraversableAmetysObject parent, String name) throws AmetysRepositoryException
293    {
294        if (parent instanceof DefaultZone)
295        {
296            ModifiableZone parentZone = (ModifiableZone) parent;
297            ModifiableZoneItem cZoneItem = parentZone.addZoneItem();
298            
299            ZoneType type = getType();
300            cZoneItem.setType(type);
301            
302            // Copy view parameters
303            getZoneItemParametersHolder().copyTo(cZoneItem.getZoneItemParametersHolder());
304
305            ViewParametersManager viewParametersManager = _getFactory().getViewParametersManager();
306            if (ZoneType.SERVICE.equals(type))
307            {
308                cZoneItem.setServiceId(getServiceId());
309                getServiceParameters().copyTo(cZoneItem.getServiceParameters());
310                
311                // Copy view parameters
312                viewParametersManager.copyServiceViewParameters(this, cZoneItem);
313            }
314            else
315            {
316                Content content = getContent();
317                if (content instanceof WebContent)
318                {
319                    WebContent webContent = (WebContent) content;
320                    if (getZone().getPage().getSiteName().equals(webContent.getSiteName()))
321                    {
322                        if (webContent instanceof SharedContent)
323                        {
324                            // No need to copy a shared content, set the reference to the shared content
325                            cZoneItem.setContent(webContent);
326                            cZoneItem.setViewName(getViewName());
327                        }
328                        else if (webContent instanceof DefaultContent)
329                        {
330                            Content cContent = ((DefaultContent) webContent).copyTo(webContent.getSite().getRootContents(), null);
331                            cZoneItem.setContent(cContent);
332                            
333                            // Update references to AmetysObject in RichText
334                            _getFactory().getCopySiteComponent().updateLinksInRichText(getZone().getPage(), cZoneItem.getZone().getPage(), content, cContent);
335                        }
336                    }
337                    else
338                    {
339                        // Copy reference  
340                        cZoneItem.setContent(getContent());
341                    }
342                }
343                else
344                {
345                    // Copy reference
346                    cZoneItem.setContent(getContent());
347                }
348                
349                String viewName = getViewName();
350                if (viewName != null)
351                {
352                    cZoneItem.setViewName(viewName);
353                }
354                
355                // Copy view parameters
356                viewParametersManager.copyContentViewParameters(this, cZoneItem);
357            }
358            
359            return cZoneItem;
360        }
361        else
362        {
363            throw new AmetysRepositoryException("Can not copy a zone item " + getId() + " of type DefaultZoneItem to something that is not a DefaultZone " + parent.getId());
364        }
365    }
366
367    @Override
368    public boolean canMoveTo(AmetysObject newParent) throws AmetysRepositoryException
369    {
370        return newParent instanceof DefaultZone;
371    }
372    
373    @Override
374    public void moveTo(AmetysObject newParent, boolean renameIfExist) throws AmetysRepositoryException, RepositoryIntegrityViolationException
375    {
376        if (!canMoveTo(newParent))
377        {
378            throw new AmetysRepositoryException("Can not move a zone item " + getId() + " of type DefaultZoneItem to something that is not a DefaultZone " + newParent.getId());
379        }
380        
381        Node node = getNode();
382
383        try
384        {
385            DefaultZone newParentZone = (DefaultZone) newParent;
386                
387            // Move node
388            node.getSession().move(node.getPath(), newParentZone.getNode().getPath() + "/" + DefaultZone.ZONEITEMS_NODE_NAME + "/" + node.getName());
389        }
390        catch (RepositoryException e)
391        {
392            throw new AmetysRepositoryException("Can not move DefaultZoneItem " + getId() + " to DefaultZone " + newParent.getId(), e);
393        }
394    }
395    
396    @Override
397    public void orderBefore(AmetysObject siblingObject) throws AmetysRepositoryException
398    {
399        if (siblingObject != null && !(siblingObject instanceof JCRAmetysObject))
400        {
401            throw new AmetysRepositoryException(String.format("Unable to order zone item '%s' before sibling '%s' (sibling is not a JCRAmetysObject)", this.getId(), siblingObject.getId()));
402        }
403        
404        Node node = getNode();
405        Node siblingNode = siblingObject != null ? ((JCRAmetysObject) siblingObject).getNode() : null;
406        
407        try
408        {
409            node.getParent().orderBefore(node.getName() + "[" + node.getIndex() + "]", siblingNode != null ? siblingNode.getName() + "[" + siblingNode.getIndex() + "]" : null);
410        }
411        catch (RepositoryException e)
412        {
413            throw new AmetysRepositoryException(String.format("Unable to order zone item '%s' before sibling '%s'", this.getId(), siblingObject != null ? siblingObject.getId() : ""), e);
414        }
415    }
416    
417    @Override
418    public Zone getZone()
419    {
420        return getParent().getParent();
421    }
422
423    public ModifiableModelAwareDataHolder getContentViewParametersHolder(String contentViewName) throws AmetysRepositoryException
424    {
425        if (getType() != ZoneType.CONTENT)
426        {
427            throw new AmetysRepositoryException("Can't get content view parameters from a not content zone item");
428        }
429        
430        JCRRepositoryData zoneItemRepositoryData = new JCRRepositoryData(getNode());
431        
432        Content content = getContent();
433        
434        // Get the skin Id
435        Page page = getZone().getPage();
436        String skinId = page.getSite().getSkinId();
437        
438        ViewParametersManager viewParametersManager = _getFactory().getViewParametersManager();
439        Optional<ViewParametersModel> contentViewParameters = viewParametersManager.getContentViewParametersModel(skinId, content, contentViewName);
440        if (contentViewParameters.isEmpty())
441        {
442            contentViewParameters = Optional.of(new ViewParametersModel("content-no-param", new View(), MapUtils.EMPTY_SORTED_MAP));
443        }
444        
445        return viewParametersManager.getContentViewParametersHolder(zoneItemRepositoryData, contentViewName, contentViewParameters.get());
446    }
447
448    public ModifiableModelAwareDataHolder getServiceViewParametersHolder(String serviceViewName) throws AmetysRepositoryException
449    {
450        if (getType() != ZoneType.SERVICE)
451        {
452            throw new AmetysRepositoryException("Can't get service view parameters from a not service zone item");
453        }
454        
455        JCRRepositoryData zoneItemRepositoryData = new JCRRepositoryData(getNode());
456        
457        String serviceId = getServiceId();
458        
459        // Get the skin Id
460        Page page = getZone().getPage();
461        String skinId = page.getSite().getSkinId();
462        
463        ViewParametersManager viewParametersManager = _getFactory().getViewParametersManager();
464        Optional<ViewParametersModel> serviceViewParameters = viewParametersManager.getServiceViewParametersModel(skinId, serviceId, serviceViewName);
465        if (serviceViewParameters.isEmpty())
466        {
467            serviceViewParameters = Optional.of(new ViewParametersModel("service-no-param", new View(), MapUtils.EMPTY_SORTED_MAP));
468        }
469        
470        return viewParametersManager.getServiceViewParametersHolder(zoneItemRepositoryData, serviceViewName, serviceViewParameters.get());
471    }
472}