001/*
002 *  Copyright 2015 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.plugins.survey.repository;
017
018import java.util.ArrayList;
019import java.util.Date;
020import java.util.GregorianCalendar;
021import java.util.List;
022import java.util.stream.Collectors;
023
024import javax.jcr.Node;
025import javax.jcr.PathNotFoundException;
026import javax.jcr.RepositoryException;
027
028import org.ametys.cms.indexing.solr.SolrAclCacheUninfluentialObject;
029import org.ametys.plugins.repository.AmetysObject;
030import org.ametys.plugins.repository.AmetysObjectIterable;
031import org.ametys.plugins.repository.AmetysRepositoryException;
032import org.ametys.plugins.repository.ChainedAmetysObjectIterable;
033import org.ametys.plugins.repository.ModifiableTraversableAmetysObject;
034import org.ametys.plugins.repository.RepositoryConstants;
035import org.ametys.web.repository.SiteAwareAmetysObject;
036import org.ametys.web.repository.site.Site;
037
038/**
039 * {@link AmetysObject} representing a survey
040 */
041@SolrAclCacheUninfluentialObject
042public class Survey extends AbstractSurveyElement<SurveyFactory> implements SiteAwareAmetysObject
043{
044    /** Constants for title metadata. */
045    private static final String __PROPERTY_TITLE = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":title";
046    /** Constants for header metadata. */
047    private static final String __PROPERTY_LABEL = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":label";
048    /** Constants for description metadata. */
049    private static final String __PROPERTY_DESC = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":description";
050    /** Constants for description metadata. */
051    private static final String __PROPERTY_ENDING_MSG = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":endingMessage";
052    /** Constants for private metadata. */
053    private static final String __PROPERTY_VALIDATED = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":validated";
054    /** Constants for private metadata. */
055    private static final String __PROPERTY_VALIDATION_DATE = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":validationDate";
056    /** Constants for start date metadata. */
057    private static final String __PROPERTY_START_DATE = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":startDate";
058    /** Constants for end date metadata. */
059    private static final String __PROPERTY_END_DATE = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":endDate";
060    /** Constants for private metadata. */
061    private static final String __PROPERTY_REDIRECTION = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":redirection";
062    
063    /**
064     * Creates a {@link Survey}.
065     * @param node the node backing this {@link AmetysObject}.
066     * @param parentPath the parent path in the Ametys hierarchy.
067     * @param factory the {@link SurveyFactory} which creates the AmetysObject.
068     */
069    public Survey(Node node, String parentPath, SurveyFactory factory)
070    {
071        super(node, parentPath, factory);
072    }
073
074    /**
075     * Retrieves the title.
076     * @return the title.
077     * @throws AmetysRepositoryException if an error occurs.
078     */
079    public String getTitle() throws AmetysRepositoryException
080    {
081        try
082        {
083            return getNode().getProperty(__PROPERTY_TITLE).getString();
084        }
085        catch (PathNotFoundException e)
086        {
087            return null;
088        }
089        catch (RepositoryException e)
090        {
091            throw new AmetysRepositoryException("Unable to get title property", e);
092        }
093    }
094    
095    /**
096     * Set the title.
097     * @param title the title.
098     * @throws AmetysRepositoryException if an error occurs.
099     */
100    public void setTitle(String title) throws AmetysRepositoryException
101    {
102        try
103        {
104            getNode().setProperty(__PROPERTY_TITLE, title);
105        }
106        catch (RepositoryException e)
107        {
108            throw new AmetysRepositoryException("Unable to set title property", e);
109        }
110    }
111    
112    /**
113     * Retrieves the survey label.
114     * @return the the survey label.
115     * @throws AmetysRepositoryException if an error occurs.
116     */
117    public String getLabel() throws AmetysRepositoryException
118    {
119        try
120        {
121            return getNode().getProperty(__PROPERTY_LABEL).getString();
122        }
123        catch (PathNotFoundException e)
124        {
125            return null;
126        }
127        catch (RepositoryException e)
128        {
129            throw new AmetysRepositoryException("Unable to get label property", e);
130        }
131    }
132    
133    /**
134     * Set the survey label.
135     * @param label the survey label.
136     * @throws AmetysRepositoryException if an error occurs.
137     */
138    public void setLabel(String label) throws AmetysRepositoryException
139    {
140        try
141        {
142            getNode().setProperty(__PROPERTY_LABEL, label);
143        }
144        catch (RepositoryException e)
145        {
146            throw new AmetysRepositoryException("Unable to set label property", e);
147        }
148    }
149    
150    /**
151     * Retrieves the description.
152     * @return the description.
153     * @throws AmetysRepositoryException if an error occurs.
154     */
155    public String getDescription() throws AmetysRepositoryException
156    {
157        try
158        {
159            return getNode().getProperty(__PROPERTY_DESC).getString();
160        }
161        catch (PathNotFoundException e)
162        {
163            return null;
164        }
165        catch (RepositoryException e)
166        {
167            throw new AmetysRepositoryException("Unable to get description property", e);
168        }
169    }
170    
171    /**
172     * Set the description.
173     * @param description the description.
174     * @throws AmetysRepositoryException if an error occurs.
175     */
176    public void setDescription(String description) throws AmetysRepositoryException
177    {
178        try
179        {
180            getNode().setProperty(__PROPERTY_DESC, description);
181        }
182        catch (RepositoryException e)
183        {
184            throw new AmetysRepositoryException("Unable to set description property", e);
185        }
186    }
187    
188    /**
189     * Retrieves the ending message.
190     * @return the ending message.
191     * @throws AmetysRepositoryException if an error occurs.
192     */
193    public String getEndingMessage() throws AmetysRepositoryException
194    {
195        try
196        {
197            return getNode().getProperty(__PROPERTY_ENDING_MSG).getString();
198        }
199        catch (PathNotFoundException e)
200        {
201            return null;
202        }
203        catch (RepositoryException e)
204        {
205            throw new AmetysRepositoryException("Unable to get ending message property", e);
206        }
207    }
208    
209    /**
210     * Set the ending message.
211     * @param message the ending message.
212     * @throws AmetysRepositoryException if an error occurs.
213     */
214    public void setEndingMessage(String message) throws AmetysRepositoryException
215    {
216        try
217        {
218            getNode().setProperty(__PROPERTY_ENDING_MSG, message);
219        }
220        catch (RepositoryException e)
221        {
222            throw new AmetysRepositoryException("Unable to set ending message property", e);
223        }
224    }
225    
226    /**
227     * Determines if the survey is validated.
228     * @return true if the survey is validated.
229     * @throws AmetysRepositoryException if an error occurs.
230     */
231    public boolean isValidated() throws AmetysRepositoryException
232    {
233        try
234        {
235            return getNode().getProperty(__PROPERTY_VALIDATED).getBoolean();
236        }
237        catch (PathNotFoundException e)
238        {
239            return false;
240        }
241        catch (RepositoryException e)
242        {
243            throw new AmetysRepositoryException("Unable to get validated property", e);
244        }
245    }
246    
247    /**
248     * Valid or invalid survey
249     * @param validated true to validate the survey
250     * @throws AmetysRepositoryException if an error occurs.
251     */
252    public void setValidated(boolean validated) throws AmetysRepositoryException
253    {
254        try
255        {
256            getNode().setProperty(__PROPERTY_VALIDATED, validated);
257        }
258        catch (RepositoryException e)
259        {
260            throw new AmetysRepositoryException("Unable to validate survey", e);
261        }
262    }
263    
264    /**
265     * Set the date of validation
266     * @param date The date of validation
267     * @throws AmetysRepositoryException if an error occurs.
268     */
269    public void setValidationDate(Date date) throws AmetysRepositoryException
270    {
271        try
272        {
273            if (date != null)
274            {
275                GregorianCalendar calendar = new GregorianCalendar();
276                calendar.setTime(date);
277                
278                getNode().setProperty(__PROPERTY_VALIDATION_DATE, calendar);
279            }
280            else if (getNode().hasProperty(__PROPERTY_VALIDATION_DATE))
281            {
282                getNode().getProperty(__PROPERTY_VALIDATION_DATE).remove();
283            }
284        }
285        catch (RepositoryException e)
286        {
287            throw new AmetysRepositoryException("Unable to set date of validation", e);
288        }
289    }
290    
291    /**
292     * Get the date of validation
293     * @return the date of validation
294     * @throws AmetysRepositoryException if an error occurs.
295     */
296    public Date getValidationDate () throws AmetysRepositoryException
297    {
298        try
299        {
300            return getNode().getProperty(__PROPERTY_VALIDATION_DATE).getDate().getTime();
301        }
302        catch (PathNotFoundException e)
303        {
304            return null;
305        }
306        catch (RepositoryException e)
307        {
308            throw new AmetysRepositoryException("Unable to get validation date property", e);
309        }
310    }
311
312    /**
313     * Set the start date
314     * @param date The start date
315     * @throws AmetysRepositoryException if an error occurs.
316     */
317    public void setStartDate(Date date) throws AmetysRepositoryException
318    {
319        try
320        {
321            if (date != null)
322            {
323                GregorianCalendar calendar = new GregorianCalendar();
324                calendar.setTime(date);
325                
326                getNode().setProperty(__PROPERTY_START_DATE, calendar);
327            }
328            else if (getNode().hasProperty(__PROPERTY_START_DATE))
329            {
330                getNode().getProperty(__PROPERTY_START_DATE).remove();
331            }
332        }
333        catch (RepositoryException e)
334        {
335            throw new AmetysRepositoryException("Unable to set start date", e);
336        }
337    }
338    
339    /**
340     * Get the start date
341     * @return the start date
342     * @throws AmetysRepositoryException if an error occurs.
343     */
344    public Date getStartDate () throws AmetysRepositoryException
345    {
346        try
347        {
348            return getNode().getProperty(__PROPERTY_START_DATE).getDate().getTime();
349        }
350        catch (PathNotFoundException e)
351        {
352            return null;
353        }
354        catch (RepositoryException e)
355        {
356            throw new AmetysRepositoryException("Unable to get start date property", e);
357        }
358    }
359    
360    /**
361     * Set the end date
362     * @param date The end date
363     * @throws AmetysRepositoryException if an error occurs.
364     */
365    public void setEndDate(Date date) throws AmetysRepositoryException
366    {
367        try
368        {
369            if (date != null)
370            {
371                GregorianCalendar calendar = new GregorianCalendar();
372                calendar.setTime(date);
373                
374                getNode().setProperty(__PROPERTY_END_DATE, calendar);
375            }
376            else if (getNode().hasProperty(__PROPERTY_END_DATE))
377            {
378                getNode().getProperty(__PROPERTY_END_DATE).remove();
379            }
380        }
381        catch (RepositoryException e)
382        {
383            throw new AmetysRepositoryException("Unable to set end date", e);
384        }
385    }
386
387    /**
388     * Get the end date
389     * @return the end date
390     * @throws AmetysRepositoryException if an error occurs.
391     */
392    public Date getEndDate () throws AmetysRepositoryException
393    {
394        try
395        {
396            return getNode().getProperty(__PROPERTY_END_DATE).getDate().getTime();
397        }
398        catch (PathNotFoundException e)
399        {
400            return null;
401        }
402        catch (RepositoryException e)
403        {
404            throw new AmetysRepositoryException("Unable to get end date property", e);
405        }
406    }
407    
408    /**
409     * Retrieves the redirection.
410     * @return the page id of redirection or null.
411     * @throws AmetysRepositoryException if an error occurs.
412     */
413    public String getRedirection() throws AmetysRepositoryException
414    {
415        try
416        {
417            return getNode().getProperty(__PROPERTY_REDIRECTION).getString();
418        }
419        catch (PathNotFoundException e)
420        {
421            return null;
422        }
423        catch (RepositoryException e)
424        {
425            throw new AmetysRepositoryException("Unable to get redirection property", e);
426        }
427    }
428    
429    /**
430     * Set the redirection.
431     * @param pageId the page id. Can be null to delete redirection
432     * @throws AmetysRepositoryException if an error occurs.
433     */
434    public void setRedirection(String pageId) throws AmetysRepositoryException
435    {
436        try
437        {
438            if (pageId == null)
439            {
440                if (getNode().hasProperty(__PROPERTY_REDIRECTION))
441                {
442                    getNode().getProperty(__PROPERTY_REDIRECTION).remove();
443                }
444            }
445            else
446            {
447                getNode().setProperty(__PROPERTY_REDIRECTION, pageId);
448            }
449        }
450        catch (RepositoryException e)
451        {
452            throw new AmetysRepositoryException("Unable to set redirection property", e);
453        }
454    }
455    
456    /**
457     * Get the survey pages.
458     * @return the survey pages.
459     * @throws AmetysRepositoryException if an error occurs when retrieving the pages of the survey
460     */
461    public List<SurveyPage> getPages() throws AmetysRepositoryException
462    {
463        return getChildren().stream()
464                .filter(child -> child instanceof SurveyPage)
465                .map(child -> (SurveyPage) child)
466                .collect(Collectors.toList());
467    }
468    
469    /**
470     * Get a question by its name.
471     * @param name the question name.
472     * @return the question.
473     * @throws AmetysRepositoryException if an error occurs when retrieving a question of a survey
474     */
475    public SurveyQuestion getQuestion(String name) throws AmetysRepositoryException
476    {
477        for (SurveyPage page : getPages())
478        {
479            if (page.hasChild(name))
480            {
481                return page.getQuestion(name);
482            }
483        }
484        return null;
485    }
486    
487    /**
488     * Get the survey questions.
489     * @return the survey questions.
490     * @throws AmetysRepositoryException if an error occurs when retrieving all the questions of a survey
491     */
492    public AmetysObjectIterable<SurveyQuestion> getQuestions() throws AmetysRepositoryException
493    {
494        List<AmetysObjectIterable<SurveyQuestion>> questions = new ArrayList<>();
495        
496        for (SurveyPage page : getPages())
497        {
498            questions.add(page.getQuestions());
499        }
500        
501        return new ChainedAmetysObjectIterable<>(questions);
502    }
503    
504    @Override
505    public Site getSite() throws AmetysRepositoryException
506    {
507        return getParent().getParent().getParent().getParent().getParent();
508    }
509    
510    @Override
511    public String getSiteName() throws AmetysRepositoryException
512    {
513        return getSite().getName();
514    }
515    
516    /**
517     * Get the survey language.
518     * @return the survey language.
519     */
520    public String getLanguage()
521    {
522        return getParent().getName();
523    }
524    
525    /**
526     * Returns a unique question name in the survey
527     * @param originalName The original name
528     * @return a unique question name
529     */
530    public String findUniqueQuestionName (String originalName)
531    {
532        String name = originalName;
533        int index = 2;
534        while (_hasQuestionName(name))
535        {
536            name = originalName + "-" + (index++);
537        }
538        return name;
539    }
540    
541    private boolean _hasQuestionName (String name)
542    {
543        for (SurveyPage page : getPages())
544        {
545            if (page.hasQuestion(name))
546            {
547                return true;
548            }
549        }
550        return false;
551    }
552    
553    @Override
554    public Survey copyTo(ModifiableTraversableAmetysObject parent, String name) throws AmetysRepositoryException
555    {
556        Survey survey = parent.createChild(name, "ametys:survey");
557        
558        survey.setTitle(getTitle());
559        survey.setLabel(getLabel());
560        
561        String description = getDescription();
562        if (description != null)
563        {
564            survey.setDescription(description);
565        }
566        
567        String endingMessage = getEndingMessage();
568        if (endingMessage != null)
569        {
570            survey.setEndingMessage(endingMessage);
571        }
572        
573        copyPictureTo(survey);
574        
575        survey.setValidated(false);
576        
577        for (SurveyPage surveyPage : getPages())
578        {
579            surveyPage.copyTo(survey, surveyPage.getName());
580        }
581        
582        // TODO Copy ACL ?
583        
584        return survey;
585    }
586    
587    @Override
588    public Survey copyTo(ModifiableTraversableAmetysObject parent, String name, List<String> restrictTo) throws AmetysRepositoryException
589    {
590        return copyTo(parent, name);
591    }
592}