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