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