001/*
002 *  Copyright 2012 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.cms.repository;
017
018import java.util.Date;
019import java.util.List;
020import java.util.Locale;
021import java.util.Map;
022
023import javax.jcr.Node;
024import javax.jcr.RepositoryException;
025
026import org.apache.avalon.framework.component.Component;
027import org.apache.avalon.framework.service.ServiceException;
028import org.apache.avalon.framework.service.ServiceManager;
029import org.apache.avalon.framework.service.Serviceable;
030
031import org.ametys.cms.content.references.OutgoingReferences;
032import org.ametys.cms.contenttype.ContentTypesHelper;
033import org.ametys.cms.contenttype.MetadataDefinition;
034import org.ametys.cms.contenttype.MetadataType;
035import org.ametys.core.user.UserIdentity;
036import org.ametys.plugins.repository.AmetysRepositoryException;
037import org.ametys.plugins.repository.RepositoryConstants;
038
039
040/**
041 * Provides helper methods to use the {@link ModifiableContent} API on {@link DefaultContent}s.
042 */
043public class ModifiableContentHelper implements Component, Serviceable
044{
045    /** The Avalon role */
046    public static final String ROLE = ModifiableContentHelper.class.getName();
047    
048    /** Constants for the root outgoing references node */
049    public static final String METADATA_ROOT_OUTGOING_REFERENCES = "root-outgoing-references";
050    
051    /** Constants for the outgoing references node */
052    public static final String METADATA_OUTGOING_REFERENCES = "outgoing-references";
053    
054    /** Constants for the outgoing references path property */
055    public static final String METADATA_OUTGOING_REFERENCES_PATH_PROPERTY = "path";
056    
057    /** Constants for the outgoing reference property */
058    public static final String METADATA_OUTGOING_REFERENCE_PROPERTY = "reference";
059    
060    /** Constants for the outgoing reference property */
061    public static final String METADATA_OUTGOING_REFERENCE_NODETYPE = "reference";
062    
063    /** Constants for language Metadata* */
064    public static final String METADATA_LANGUAGE = "language";
065    
066    /** Constants for title Metadata* */
067    public static final String METADATA_TITLE = "title";
068    
069    /** Constants for author Metadata* */
070    public static final String METADATA_CREATOR = "creator";
071    
072    /** Constants for lastModified Metadata* */
073    public static final String METADATA_CREATION = "creationDate";
074
075    /** Constants for lastValidationDate Metadata* */
076    public static final String METADATA_LAST_VALIDATION = "lastValidationDate";
077    
078    /** Constants for lastMajorValidationDate Metadata* */
079    public static final String METADATA_LAST_MAJORVALIDATION = "lastMajorValidationDate";
080    
081    /** Constants for last contributor Metadata* */
082    public static final String METADATA_CONTRIBUTOR = "contributor";
083    
084    /** Constants for lastModified Metadata* */
085    public static final String METADATA_MODIFIED = "lastModified";
086
087    private ContentTypesHelper _contentTypesHelper;
088    
089    public void service(ServiceManager manager) throws ServiceException
090    {
091        _contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE);
092    }
093    
094    /**
095     * Set a {@link DefaultContent} type.
096     * @param content the {@link DefaultContent} to set.
097     * @param type the type.
098     * @throws AmetysRepositoryException if an error occurs.
099     */
100    public void setType(DefaultContent content, String type) throws AmetysRepositoryException
101    {
102        setTypes(content, new String[] {type});
103    }
104    
105    /**
106     * Set {@link DefaultContent} types.
107     * @param content the {@link DefaultContent} to set.
108     * @param types the types.
109     * @throws AmetysRepositoryException if an error occurs.
110     */
111    public void setTypes(DefaultContent content, String[] types) throws AmetysRepositoryException
112    {
113        Node node = content.getNode();
114        
115        try
116        {
117            content._checkLock();
118            node.setProperty(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + DefaultContent.METADATA_CONTENTTYPE, types);
119        }
120        catch (RepositoryException e)
121        {
122            throw new AmetysRepositoryException("Unable to set contentType property", e);
123        }
124    }
125    
126    /**
127     * Set {@link DefaultContent} mixins.
128     * @param content the {@link DefaultContent} to set.
129     * @param mixins the mixins.
130     * @throws AmetysRepositoryException if an error occurs.
131     */
132    public void setMixinTypes(DefaultContent content, String[] mixins) throws AmetysRepositoryException
133    {
134        Node node = content.getNode();
135        
136        try
137        {
138            content._checkLock();
139            node.setProperty(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + DefaultContent.METADATA_MIXINCONTENTTYPES, mixins);
140        }
141        catch (RepositoryException e)
142        {
143            throw new AmetysRepositoryException("Unable to set mixins property", e);
144        }
145    }
146    
147    /**
148     * Set a {@link DefaultContent} language.
149     * @param content the {@link DefaultContent} to modify.
150     * @param language the language to set.
151     * @throws AmetysRepositoryException if an error occurs.
152     */
153    public void setLanguage(DefaultContent content, String language) throws AmetysRepositoryException
154    {
155        Node node = content.getNode();
156        
157        try
158        {
159            content._checkLock();
160            node.setProperty(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + METADATA_LANGUAGE, language);
161        }
162        catch (RepositoryException e)
163        {
164            throw new AmetysRepositoryException("Unable to set language property", e);
165        }
166    }
167    
168    /**
169     * Set a {@link DefaultContent} title for the given locale. 
170     * Be careful ! Can be invoked only if the metadata title already exists.
171     * @param content the {@link DefaultContent} to set.
172     * @param title the title to set.
173     * @param locale The locale. Can be null if the content is not a multilingual content.
174     * @throws AmetysRepositoryException if an error occurs.
175     */
176    public void setTitle(DefaultContent content, String title, Locale locale) throws AmetysRepositoryException
177    {
178        MetadataDefinition metaDef = _contentTypesHelper.getMetadataDefinition(METADATA_TITLE, content);
179        if (metaDef == null)
180        {
181            throw new IllegalArgumentException("Mandatory title metadata definition is not defined");
182        }
183        
184        MetadataType type = metaDef.getType();
185        if (MetadataType.MULTILINGUAL_STRING.equals(type))
186        {
187            if (locale == null)
188            {
189                throw new IllegalArgumentException("Cannot set a title with null locale on a multilingual title");
190            }
191            content.getMetadataHolder().setMetadata(METADATA_TITLE, title, locale);
192        }
193        else
194        {
195            content.getMetadataHolder().setMetadata(METADATA_TITLE, title);
196        }
197    }
198    
199    /**
200     * Set the title of non-multilingual {@link DefaultContent}.
201     * Be careful ! Use only if content's title is not a multilingual string. If not sure use {@link #setTitle(DefaultContent, String, Locale)} instead.
202     * @param content the {@link DefaultContent} to set.
203     * @param title the title to set.
204     * @throws AmetysRepositoryException if an error occurs.
205     */
206    public void setTitle(DefaultContent content, String title) throws AmetysRepositoryException
207    {
208        setTitle(content, title, null);
209    }
210    
211    /**
212     * Copy the title of the source content to the target content
213     * @param srcContent The source content
214     * @param targetContent The target content
215     * @throws AmetysRepositoryException if an error occurs.
216     */
217    public void copyTitle(Content srcContent, ModifiableContent targetContent) throws AmetysRepositoryException
218    {
219        org.ametys.plugins.repository.metadata.CompositeMetadata.MetadataType type = srcContent.getMetadataHolder().getType(METADATA_TITLE);
220        if (MetadataType.MULTILINGUAL_STRING.equals(type))
221        {
222            targetContent.getMetadataHolder().setMetadata(METADATA_TITLE, srcContent.getMetadataHolder().getMultilingualString(METADATA_TITLE));
223        }
224        else
225        {
226            targetContent.getMetadataHolder().setMetadata(METADATA_TITLE, srcContent.getTitle());
227        }
228    }
229    
230    /**
231     * Set a {@link DefaultContent} user.
232     * @param content the {@link DefaultContent} to set.
233     * @param user the user to set.
234     * @throws AmetysRepositoryException if an error occurs.
235     */
236    public void setCreator(DefaultContent content, UserIdentity user) throws AmetysRepositoryException
237    {
238        try
239        {
240            Node creatorNode = null;
241            if (content.getNode().hasNode(RepositoryConstants.NAMESPACE_PREFIX + ':' + METADATA_CREATOR))
242            {
243                creatorNode = content.getNode().getNode(RepositoryConstants.NAMESPACE_PREFIX + ':' + METADATA_CREATOR);
244            }
245            else
246            {
247                creatorNode = content.getNode().addNode(RepositoryConstants.NAMESPACE_PREFIX + ':' + METADATA_CREATOR, RepositoryConstants.USER_NODETYPE);
248            }
249            creatorNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX + ":login", user.getLogin());
250            creatorNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX + ":population", user.getPopulationId());
251        }
252        catch (RepositoryException e)
253        {
254            throw new AmetysRepositoryException("Error setting the creator property.", e);
255        }
256    }
257    
258    /**
259     * Set a {@link DefaultContent} creation date.
260     * @param content the {@link DefaultContent} to set.
261     * @param creationDate the creation date to set.
262     * @throws AmetysRepositoryException if an error occurs.
263     */
264    public void setCreationDate(DefaultContent content, Date creationDate) throws AmetysRepositoryException
265    {
266        content.getMetadataHolder().setMetadata(METADATA_CREATION, creationDate);
267    }
268    
269    /**
270     * Set a {@link DefaultContent} contributor.
271     * @param content the {@link DefaultContent} to set.
272     * @param user the contributor to set.
273     * @throws AmetysRepositoryException if an error occurs.
274     */
275    public void setLastContributor(DefaultContent content, UserIdentity user) throws AmetysRepositoryException
276    {
277        try
278        {
279            Node creatorNode = null;
280            if (content.getNode().hasNode(RepositoryConstants.NAMESPACE_PREFIX + ':' + METADATA_CONTRIBUTOR))
281            {
282                creatorNode = content.getNode().getNode(RepositoryConstants.NAMESPACE_PREFIX + ':' + METADATA_CONTRIBUTOR);
283            }
284            else
285            {
286                creatorNode = content.getNode().addNode(RepositoryConstants.NAMESPACE_PREFIX + ':' + METADATA_CONTRIBUTOR, RepositoryConstants.USER_NODETYPE);
287            }
288            creatorNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX + ":login", user.getLogin());
289            creatorNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX + ":population", user.getPopulationId());
290        }
291        catch (RepositoryException e)
292        {
293            throw new AmetysRepositoryException("Error setting the last contributor property.", e);
294        }
295    }
296    
297    /**
298     * Set a {@link DefaultContent} last modification date.
299     * @param content the {@link DefaultContent} to set.
300     * @param lastModified the last modification date to set.
301     * @throws AmetysRepositoryException if an error occurs.
302     */
303    public void setLastModified(DefaultContent content, Date lastModified) throws AmetysRepositoryException
304    {
305        content.getMetadataHolder().setMetadata(METADATA_MODIFIED, lastModified);
306    }
307    
308    /**
309     * Set a {@link DefaultContent} last validation date.
310     * @param content the {@link DefaultContent} to set.
311     * @param validationDate the last validation date.
312     * @throws AmetysRepositoryException if an error occurs.
313     */
314    public void setLastValidationDate(DefaultContent content, Date validationDate) throws AmetysRepositoryException
315    {
316        content.getMetadataHolder().setMetadata(METADATA_LAST_VALIDATION, validationDate);
317    }
318    
319    /**
320     * Set a {@link DefaultContent} last major validation date.
321     * @param content the {@link DefaultContent} to set.
322     * @param validationDate the last major validation date.
323     * @throws AmetysRepositoryException if an error occurs.
324     */
325    public void setLastMajorValidationDate(DefaultContent content, Date validationDate) throws AmetysRepositoryException
326    {
327        content.getMetadataHolder().setMetadata(METADATA_LAST_MAJORVALIDATION, validationDate);
328    }
329    
330    /**
331     * Store the outgoing references on the content.
332     * @param content The content concerned by these outgoing references.
333     * @param references A non null map of outgoing references grouped by metadata (key are metadata path)
334     * @throws AmetysRepositoryException if an error occurs.
335     */
336    public void setOutgoingReferences(DefaultContent content, Map<String, OutgoingReferences> references) throws AmetysRepositoryException
337    {
338        try
339        {
340            Node contentNode = content.getNode();
341            if (contentNode.hasNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + METADATA_ROOT_OUTGOING_REFERENCES))
342            {
343                contentNode.getNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + METADATA_ROOT_OUTGOING_REFERENCES).remove();
344            }
345            
346            if (references.size() != 0)
347            {
348                Node rootOutgoingRefsNode = contentNode.addNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + METADATA_ROOT_OUTGOING_REFERENCES);
349                for (String path : references.keySet())
350                {
351                    // Outgoing references node creation (for the given path)
352                    Node outgoingRefsNode = rootOutgoingRefsNode.addNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + METADATA_OUTGOING_REFERENCES);
353                    outgoingRefsNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + METADATA_OUTGOING_REFERENCES_PATH_PROPERTY, path);
354                    
355                    // Reference nodes per type
356                    OutgoingReferences outgoingReferences = references.get(path);
357                    
358                    for (String type : outgoingReferences.keySet())
359                    {
360                        List<String> referenceValues = outgoingReferences.get(type);
361                        if (referenceValues != null && !referenceValues.isEmpty())
362                        {
363                            Node outgoingReferenceNode = outgoingRefsNode.addNode(type, RepositoryConstants.NAMESPACE_PREFIX + ':' + METADATA_OUTGOING_REFERENCE_NODETYPE);
364                            outgoingReferenceNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX + ':' + METADATA_OUTGOING_REFERENCE_PROPERTY, referenceValues.toArray(new String[referenceValues.size()]));
365                        }
366                    }
367                }
368            }
369        }
370        catch (RepositoryException e)
371        {
372            throw new AmetysRepositoryException(e);
373        }
374    }
375}