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.dao;
017
018import java.util.ArrayList;
019import java.util.HashMap;
020import java.util.Iterator;
021import java.util.List;
022import java.util.Map;
023
024import javax.jcr.Node;
025import javax.jcr.NodeIterator;
026import javax.jcr.Repository;
027import javax.jcr.RepositoryException;
028import javax.jcr.Session;
029import javax.jcr.query.Query;
030
031import org.apache.avalon.framework.service.ServiceException;
032import org.apache.avalon.framework.service.ServiceManager;
033import org.apache.commons.lang.StringUtils;
034
035import org.ametys.core.observation.Event;
036import org.ametys.core.ui.Callable;
037import org.ametys.plugins.repository.AmetysObjectIterable;
038import org.ametys.plugins.repository.AmetysRepositoryException;
039import org.ametys.plugins.repository.RepositoryConstants;
040import org.ametys.plugins.repository.UnknownAmetysObjectException;
041import org.ametys.plugins.repository.jcr.NameHelper;
042import org.ametys.plugins.repository.provider.AbstractRepository;
043import org.ametys.plugins.survey.SurveyEvents;
044import org.ametys.plugins.survey.repository.Survey;
045import org.ametys.plugins.survey.repository.SurveyPage;
046import org.ametys.plugins.survey.repository.SurveyQuestion;
047import org.ametys.plugins.survey.repository.SurveyQuestion.QuestionType;
048import org.ametys.plugins.survey.repository.SurveyRule;
049import org.ametys.plugins.survey.repository.SurveyRule.RuleType;
050
051/**
052 * DAO for manipulating survey pages.
053 *
054 */
055public class PageDAO extends AbstractDAO
056{
057    /** The Avalon role */
058    public static final String ROLE = PageDAO.class.getName();
059    
060    /** The repository */
061    private Repository _repository;
062    
063    /** The Question DAO */
064    private QuestionDAO _questionDAO;
065    
066    @Override
067    public void service(ServiceManager serviceManager) throws ServiceException
068    {
069        super.service(serviceManager);
070        _repository = (Repository) serviceManager.lookup(AbstractRepository.ROLE);
071        _questionDAO = (QuestionDAO) serviceManager.lookup(QuestionDAO.ROLE);
072    }
073    
074    /**
075     * Gets properties of a survey page
076     * @param id The id of the survey page
077     * @return The properties
078     */
079    @Callable
080    public Map<String, Object> getPage (String id)
081    {
082        SurveyPage page = _resolver.resolveById(id);
083        
084        return getPage(page);
085    }
086    
087    /**
088     * Gets properties of a survey page
089     * @param page The survey page
090     * @return The properties
091     */
092    public Map<String, Object> getPage (SurveyPage page)
093    {
094        Map<String, Object> properties = new HashMap<>();
095        
096        properties.put("id", page.getId());
097        properties.put("label", page.getLabel());
098        properties.put("title", page.getTitle());
099        properties.put("description", page.getDescription());
100        
101        properties.putAll(getPictureInfo(page));
102        
103        return properties;
104    }
105    
106    /**
107     * Determines if a page is the last of survey's pages.
108     * @param id The page id
109     * @return True if the page is the last one. 
110     */
111    @Callable
112    public boolean isLastPage (String id)
113    {
114        SurveyPage page = _resolver.resolveById(id);
115        
116        Survey survey = page.getParent();
117        AmetysObjectIterable<SurveyPage> pages = survey.getChildren();
118        Iterator<SurveyPage> it = pages.iterator();
119        
120        SurveyPage lastPage = null;
121        while (it.hasNext())
122        {
123            lastPage = it.next();
124        }
125        
126        boolean isLast = lastPage != null && id.equals(lastPage.getId());
127        return isLast;
128    }
129    
130    /**
131     * Creates a survey page.
132     * @param values The survey page's values
133     * @return The id of the created survey page
134     * @throws Exception if an exception occurs during the page creation process
135     */
136    @Callable
137    public Map<String, String> createPage (Map<String, Object> values) throws Exception
138    {
139        Map<String, String> result = new HashMap<>();
140        
141        String surveyId = StringUtils.defaultString((String) values.get("surveyId"));
142        Survey survey = _resolver.resolveById(surveyId);
143        
144        String label = StringUtils.defaultString((String) values.get("label"));
145        String originalName = NameHelper.filterName(label);
146        
147        // Find unique name
148        String name = originalName;
149        int index = 2;
150        while (survey.hasChild(name))
151        {
152            name = originalName + "-" + (index++);
153        }
154        
155        SurveyPage page = survey.createChild(name, "ametys:survey-page");
156        _setValues(page, values);
157        
158        survey.saveChanges();
159        
160        Map<String, Object> eventParams = new HashMap<>();
161        eventParams.put("survey", survey);
162        _observationManager.notify(new Event(SurveyEvents.SURVEY_MODIFIED, _getCurrentUser(), eventParams));
163        
164        result.put("id", page.getId());
165        
166        return result;
167    }
168    
169    /**
170     * Edits a survey page.
171     * @param values The survey page's values
172     * @return The id of the edited survey page and the id of its survey parent
173     */
174    @Callable
175    public Map<String, String> editPage (Map<String, Object> values)
176    {
177        Map<String, String> result = new HashMap<>();
178        
179        String id = StringUtils.defaultString((String) values.get("id"));
180        SurveyPage page = _resolver.resolveById(id);
181        
182        _setValues(page, values);
183        
184        page.saveChanges();
185        
186        Map<String, Object> eventParams = new HashMap<>();
187        eventParams.put("survey", page.getSurvey());
188        _observationManager.notify(new Event(SurveyEvents.SURVEY_MODIFIED, _getCurrentUser(), eventParams));
189        
190        result.put("id", page.getId());
191        result.put("surveyId", page.getSurvey().getId());
192        
193        return result;
194    }
195    
196    private void _setValues (SurveyPage page, Map<String, Object> values)
197    {
198        page.setTitle(StringUtils.defaultString((String) values.get("title")));
199        page.setLabel(StringUtils.defaultString((String) values.get("label")));
200        page.setDescription(StringUtils.defaultString((String) values.get("description")));
201        
202        page.setPictureAlternative(StringUtils.defaultString((String) values.get("picture-alternative")));
203        setPicture(page, StringUtils.defaultString((String) values.get("picture")));
204    }
205    
206    /**
207     * Copies and pastes a survey page.
208     * @param surveyId The id of the survey, target of the copy
209     * @param pageId The id of the page to copy
210     * @return The id of the created page
211     */
212    @Callable
213    public Map<String, String> copyPage(String surveyId, String pageId)
214    {
215        Map<String, String> result = new HashMap<>();
216        
217        SurveyPage originalPage = _resolver.resolveById(pageId);
218        Survey parentSurvey = _resolver.resolveById(surveyId);
219        
220        // Find unique name
221        String originalName = originalPage.getName();
222        String name = originalName;
223        int index = 2;
224        while (parentSurvey.hasChild(name))
225        {
226            name = originalName + "-" + (index++);
227        }
228        
229        SurveyPage cPage = originalPage.copyTo(parentSurvey, name);
230        
231        Survey originalSurvey = originalPage.getSurvey();
232        if (!originalSurvey.getId().equals(parentSurvey.getId()))
233        {
234            // Update rules references after copy
235            updateReferencesAfterCopy (originalPage.getSurvey(), parentSurvey, cPage);
236        }
237        
238        parentSurvey.saveChanges();
239        
240        Map<String, Object> eventParams = new HashMap<>();
241        eventParams.put("survey", parentSurvey);
242        _observationManager.notify(new Event(SurveyEvents.SURVEY_MODIFIED, _getCurrentUser(), eventParams));
243        
244        result.put("id", cPage.getId());
245        
246        return result;
247    }
248    
249    /**
250     * Deletes a survey page.
251     * @param id The id of the survey page to delete
252     * @return The id of the deleted survey page and the id of its survey parent
253     */
254    @Callable
255    public Map<String, String> deletePage (String id)
256    {
257        Map<String, String> result = new HashMap<>();
258        
259        SurveyPage page = _resolver.resolveById(id);
260        Survey survey = page.getParent();
261        
262        page.remove();
263        
264        // Remove rules references
265        _removeReferencesFromPages (id);
266        _removeReferencesFromQuestions(id);
267        
268        survey.saveChanges();
269        
270        Map<String, Object> eventParams = new HashMap<>();
271        eventParams.put("survey", survey);
272        _observationManager.notify(new Event(SurveyEvents.SURVEY_MODIFIED, _getCurrentUser(), eventParams));
273        
274        result.put("id", id);
275        result.put("surveyId", survey.getId());
276        
277        return result;
278    }
279    
280    /**
281     * Adds a a new rule to a page.
282     * @param id The id of the page
283     * @param rule The rule type
284     * @param page The page to jump or skip
285     * @return An empty map
286     */
287    @Callable
288    public Map<String, Object> addRule (String id, String rule, String page)
289    {
290        SurveyPage surveyPage = _resolver.resolveById(id);
291        
292        surveyPage.setRule(RuleType.valueOf(rule), page);
293        surveyPage.saveChanges();
294        
295        Map<String, Object> eventParams = new HashMap<>();
296        eventParams.put("survey", surveyPage.getSurvey());
297        _observationManager.notify(new Event(SurveyEvents.SURVEY_MODIFIED, _getCurrentUser(), eventParams));
298        
299        return new HashMap<>();
300    }
301    
302    /**
303     * Deletes a rule to a page
304     * @param id The id of the page
305     * @return An empty map
306     */
307    @Callable
308    public Map<String, Object> deleteRule (String id)
309    {
310        SurveyPage surveyPage = _resolver.resolveById(id);
311        
312        surveyPage.deleteRule();
313        surveyPage.saveChanges();
314        
315        Map<String, Object> eventParams = new HashMap<>();
316        eventParams.put("survey", surveyPage.getSurvey());
317        _observationManager.notify(new Event(SurveyEvents.SURVEY_MODIFIED, _getCurrentUser(), eventParams));
318        
319        return new HashMap<>();
320    }
321    
322    /**
323     * Gets the rule for a survey page.
324     * @param id The id of the survey page.
325     * @return The rule, or null
326     */
327    @Callable
328    public Map<String, Object> getRule (String id)
329    {
330        Map<String, Object> result = new HashMap<>();
331        
332        SurveyPage page = _resolver.resolveById(id);
333        SurveyRule rule = page.getRule();
334        
335        if (rule != null)
336        {
337            result.put("type", rule.getType().name());
338            String pageId = rule.getPage();
339            if (pageId != null)
340            {
341                try
342                {
343                    SurveyPage pageAO = _resolver.resolveById(pageId);
344                    result.put("page", pageId);
345                    result.put("pageName", pageAO.getLabel());
346                }
347                catch (UnknownAmetysObjectException e)
348                {
349                    // The page does not exist anymore
350                }
351            }
352        }
353        else
354        {
355            result = null;
356        }
357        
358        return result;
359    }
360    
361    /**
362     * Gets the branches for a survey page.
363     * @param id The id of the survey page.
364     * @return The branches
365     */
366    @Callable
367    public Map<String, Object> getBranches (String id)
368    {
369        Map<String, Object> result = new HashMap<>();
370        
371        SurveyPage page = _resolver.resolveById(id);
372        
373        result.put("id", page.getId());
374        
375        List<Object> questions = new ArrayList<>();
376        AmetysObjectIterable<SurveyQuestion> questionsAO = page.getChildren();
377        int index = 1;
378        for (SurveyQuestion question : questionsAO)
379        {
380            if (question.getType() == QuestionType.SINGLE_CHOICE || question.getType() == QuestionType.MULTIPLE_CHOICE)
381            {
382                questions.add(_questionDAO.getRules(question.getId(), index));
383            }
384            index++;
385        }
386        result.put("questions", questions);
387        
388        // SAX page rule
389        result.put("rule", getRule(id));
390        
391        return result;
392    }
393    
394    private void _removeReferencesFromPages (String pageId)
395    {
396        Session session = null;
397        String jcrQuery = "//element(*, ametys:survey-page)/element(*, ametys:survey-rule)[@ametys-internal:page='" + pageId + "']"; 
398        
399        try
400        {
401            session = _repository.login();
402            @SuppressWarnings("deprecation")
403            Query query = session.getWorkspace().getQueryManager().createQuery(jcrQuery, Query.XPATH);
404            
405            NodeIterator nodes = query.execute().getNodes();
406            while (nodes.hasNext())
407            {
408                Node ruleNode = nodes.nextNode();
409                Node pageNode = ruleNode.getParent();
410                
411                pageNode.getNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":rule").remove();
412                pageNode.getSession().save();
413            }
414        }
415        catch (RepositoryException ex)
416        {
417            if (session != null)
418            {
419                session.logout();
420            }
421
422            throw new AmetysRepositoryException("An error occurred executing the JCR query : " + jcrQuery, ex);
423        }
424    }
425    
426    private void _removeReferencesFromQuestions (String pageId)
427    {
428        Session session = null;
429        String jcrQuery = "//element(*, ametys:survey-question)//element(*, ametys:survey-rule)[@ametys-internal:page='" + pageId + "']"; 
430        
431        try
432        {
433            session = _repository.login();
434            @SuppressWarnings("deprecation")
435            Query query = session.getWorkspace().getQueryManager().createQuery(jcrQuery, Query.XPATH);
436            
437            NodeIterator nodes = query.execute().getNodes();
438            while (nodes.hasNext())
439            {
440                Node ruleNode = nodes.nextNode();
441                Node questionNode = ruleNode.getParent().getParent();
442                
443                questionNode.getNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":rules").getNode(ruleNode.getName()).remove();
444                questionNode.getSession().save();
445            }
446        }
447        catch (RepositoryException ex)
448        {
449            if (session != null)
450            {
451                session.logout();
452            }
453
454            throw new AmetysRepositoryException("An error occurred executing the JCR query : " + jcrQuery, ex);
455        }
456    }
457}