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