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