001/*
002 *  Copyright 2011 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.answer;
017
018import java.net.URI;
019import java.net.URISyntaxException;
020import java.text.DateFormat;
021import java.time.ZoneId;
022import java.time.ZonedDateTime;
023import java.util.ArrayList;
024import java.util.Arrays;
025import java.util.Collection;
026import java.util.Collections;
027import java.util.Date;
028import java.util.HashMap;
029import java.util.HashSet;
030import java.util.Iterator;
031import java.util.LinkedHashMap;
032import java.util.List;
033import java.util.Locale;
034import java.util.Map;
035import java.util.Set;
036import java.util.regex.Pattern;
037
038import org.apache.avalon.framework.parameters.Parameters;
039import org.apache.avalon.framework.service.ServiceException;
040import org.apache.avalon.framework.service.ServiceManager;
041import org.apache.cocoon.acting.ServiceableAction;
042import org.apache.cocoon.environment.Cookie;
043import org.apache.cocoon.environment.ObjectModelHelper;
044import org.apache.cocoon.environment.Redirector;
045import org.apache.cocoon.environment.Request;
046import org.apache.cocoon.environment.Response;
047import org.apache.cocoon.environment.SourceResolver;
048import org.apache.cocoon.environment.http.HttpCookie;
049import org.apache.commons.lang.StringUtils;
050
051import org.ametys.core.right.RightManager;
052import org.ametys.core.user.CurrentUserProvider;
053import org.ametys.core.user.UserIdentity;
054import org.ametys.plugins.repository.AmetysObjectIterable;
055import org.ametys.plugins.repository.AmetysObjectResolver;
056import org.ametys.plugins.repository.UnknownAmetysObjectException;
057import org.ametys.plugins.survey.SurveyDateUtils;
058import org.ametys.plugins.survey.data.SurveyAnswer;
059import org.ametys.plugins.survey.data.SurveyAnswerDao;
060import org.ametys.plugins.survey.data.SurveySession;
061import org.ametys.plugins.survey.repository.Survey;
062import org.ametys.plugins.survey.repository.SurveyAccessHelper;
063import org.ametys.plugins.survey.repository.SurveyPage;
064import org.ametys.plugins.survey.repository.SurveyQuestion;
065import org.ametys.plugins.survey.repository.SurveyRule;
066import org.ametys.runtime.i18n.I18nizableText;
067import org.ametys.web.URIPrefixHandler;
068import org.ametys.web.repository.page.Page;
069
070/**
071 * Process the user answers to the survey.
072 */
073public class ProcessInputAction extends ServiceableAction
074{
075    
076    /** The name of the cookie storing the taken surveys. */
077    public static final String COOKIE_NAME = "org.ametys.survey.answeredSurveys";
078    
079    /** The ametys object resolver. */
080    protected AmetysObjectResolver _resolver;
081    /** The ametys object resolver. */
082    protected SurveyAnswerDao _answerDao;
083    /** The user provider. */
084    protected CurrentUserProvider _userProvider;
085    /** The uri prefix handler. */
086    protected URIPrefixHandler _prefixHandler;
087    /** The survey access helper */
088    protected SurveyAccessHelper _accessHelper;
089    
090    /** The plugin name */
091    protected String _pluginName;
092
093    private RightManager _rightManager;
094    
095    @Override
096    public void service(ServiceManager serviceManager) throws ServiceException
097    {
098        super.service(serviceManager);
099        _resolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE);
100        _answerDao = (SurveyAnswerDao) serviceManager.lookup(SurveyAnswerDao.ROLE);
101        _userProvider = (CurrentUserProvider) serviceManager.lookup(CurrentUserProvider.ROLE);
102        _prefixHandler = (URIPrefixHandler) serviceManager.lookup(URIPrefixHandler.ROLE);
103        _accessHelper = (SurveyAccessHelper) serviceManager.lookup(SurveyAccessHelper.ROLE);
104        _rightManager = (RightManager) serviceManager.lookup(RightManager.ROLE);
105    }
106    
107    @Override
108    public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception
109    {
110        Request request = ObjectModelHelper.getRequest(objectModel);
111        Response response = ObjectModelHelper.getResponse(objectModel);
112        
113        _pluginName = getPluginName(request);
114        
115        String surveyId = request.getParameter("surveyId");
116        if (StringUtils.isNotEmpty(surveyId))
117        {
118            // Get the survey object.
119            Survey survey = _resolver.resolveById(surveyId);
120            
121            SurveyErrors errors = new SurveyErrors();
122            
123            if (!checkAccess(survey, request, errors))
124            {
125                request.setAttribute("survey", survey);
126                request.setAttribute("survey-errors", errors);
127                return null;
128            }
129            
130            // Get the user input.
131            SurveyInput surveyInput = getInput(survey, request);
132            
133            // Validate the user input.
134            validateInput(survey, surveyInput, errors, request);
135            
136            // If there were errors in the input, store it as a request attribute and stop.
137            if (errors.hasErrors())
138            {
139                request.setAttribute("survey", survey);
140                request.setAttribute("survey-errors", errors);
141                return null;
142            }
143            
144            // Add the user session into the database.
145            _answerDao.addSession(surveyInput);
146            
147            setCookie(request, response, surveyId);
148            
149            // Redirect if necessary.
150            String redirectPageId = survey.getRedirection();
151            if (StringUtils.isNotEmpty(redirectPageId))
152            {
153                try
154                {
155                    Page page = _resolver.resolveById(redirectPageId);
156                    redirector.globalRedirect(false, _prefixHandler.getAbsoluteUriPrefix(page.getSiteName()) + "/" + page.getSitemapName() + "/" + page.getPathInSitemap() + ".html");
157                }
158                catch (UnknownAmetysObjectException e)
159                {
160                    getLogger().warn("The survey '" + survey.getId() + "' wants to redirect to the unexisting page '" + redirectPageId + "'. Redirecting to default page.", e);
161                }
162            }
163        }
164        
165        return EMPTY_MAP;
166    }
167    
168    /**
169     * Get the plugin name
170     * @param request The request
171     * @return The plugin name
172     */
173    protected String getPluginName (Request request)
174    {
175        return (String) request.getAttribute("pluginName");
176    }
177    
178    /**
179     * Check if user can answer to the survey
180     * @param survey The survey
181     * @param request The request
182     * @param errors The survey errors
183     * @return false if the access failed
184     */
185    protected boolean checkAccess(Survey survey, Request request, SurveyErrors errors)
186    {
187        UserIdentity user = getAuthenticatedUser(request);
188        
189        if (!_rightManager.hasReadAccess(user, survey))
190        {
191            // User is not authorized
192            errors.addErrors("survey-access", Collections.singletonList(new I18nizableText("plugin." + _pluginName, "PLUGINS_SURVEY_RENDER_UNAUTHORIZED")));
193            return false;
194        }
195        
196        String surveyId = survey.getId();
197        Date submissionDate = _accessHelper.getSubmissionDate(surveyId, user);
198        
199        if (submissionDate != null)
200        {
201            // The authenticated user has already answered to the survey
202            Map<String, I18nizableText> i18nParams = new HashMap<>();
203            DateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM, new Locale(survey.getLanguage()));
204            i18nParams.put("date", new I18nizableText(df.format(submissionDate)));
205            
206            errors.addErrors("survey-access", Collections.singletonList(new I18nizableText("plugin." + _pluginName, "PLUGINS_SURVEY_RENDER_ALREADY_TAKEN_ON", i18nParams)));
207            return false;
208        }
209        
210        // Finally check cookies
211        if (_accessHelper.getCookieName(request, survey) != null)
212        {
213            // Anonymous user has already answered to the survey
214            errors.addErrors("survey-access", Collections.singletonList(new I18nizableText("plugin." + _pluginName, "PLUGINS_SURVEY_RENDER_ALREADY_TAKEN")));
215            return false;
216        }
217        
218        // User can access to the survey
219        return true;
220    }
221    
222    /**
223     * Get the authenticated user
224     * @param request The request
225     * @return The authenticated user
226     */
227    protected UserIdentity getAuthenticatedUser (Request request)
228    {
229        return _userProvider.getUser();
230    }
231    
232    /**
233     * Get the user input.
234     * @param survey the survey.
235     * @param request the request.
236     * @return the user input.
237     */
238    protected SurveyInput getInput(Survey survey, Request request)
239    {
240        String clientIp = getClientIp(request);
241        UserIdentity user = getAuthenticatedUser(request);
242        
243        SurveyInput surveySession = new SurveyInput();
244        
245        List<SurveyInputAnswer> answers = new ArrayList<>();
246        
247        surveySession.setSurveyId(survey.getId());
248        surveySession.setSubmittedAt(new Date());
249        surveySession.setUser(user);
250        surveySession.setIpAddress(clientIp);
251        surveySession.setAnswerList(answers);
252        
253        try (AmetysObjectIterable<SurveyQuestion> questions = survey.getQuestions())
254        {
255            for (SurveyQuestion question : questions)
256            {
257                Map<String, Set<String>> values = getValues(question, request);
258                
259                SurveyInputAnswer answer = new SurveyInputAnswer(question, values);
260                answers.add(answer);
261            }
262            
263            return surveySession;
264        }
265    }
266    
267    /**
268     * Get an answer value from the request.
269     * @param question the corresponding question.
270     * @param request the request.
271     * @return the answer value.
272     */
273    protected Map<String, Set<String>> getValues(SurveyQuestion question, Request request)
274    {
275        Map<String, Set<String>> values = new LinkedHashMap<>();
276        
277        String name = question.getName();
278        switch (question.getType())
279        {
280            case SINGLE_MATRIX:
281            case MULTIPLE_MATRIX:
282                Collection<String> options = question.getOptions().keySet();
283                
284                for (String option : options)
285                {
286                    String paramName = name + "_" + option;
287                    String[] paramValues = request.getParameterValues(paramName);
288                    
289                    if (paramValues != null)
290                    {
291                        values.put(option, new HashSet<>(Arrays.asList(paramValues)));
292                    }
293                }
294                break;
295            case FREE_TEXT:
296            case MULTILINE_FREE_TEXT:
297                String[] textValues = request.getParameterValues(name);
298                if (textValues != null)
299                {
300                    values.put("values", new HashSet<>(Arrays.asList(textValues)));
301                }
302                break;
303            case SINGLE_CHOICE:
304            case MULTIPLE_CHOICE:
305            default:
306                List<String> valuesAsList = new ArrayList<>();
307                String[] paramValues = request.getParameterValues(name);
308                if (paramValues != null)
309                {
310                    for (String value : paramValues)
311                    {
312                        if (value.equals("__internal_other"))
313                        {
314                            valuesAsList.add(request.getParameter("__internal_other_" + name));
315                        }
316                        else
317                        {
318                            valuesAsList.add(value);
319                        }
320                    }
321                    values.put("values", new HashSet<>(valuesAsList));
322                }
323                break;
324        }
325        
326        return values;
327    }
328    
329    /**
330     * Validate the user input.
331     * @param survey the survey.
332     * @param input the user input.
333     * @param errors the errors.
334     * @param request the request.
335     */
336    protected void validateInput(Survey survey, SurveyInput input, SurveyErrors errors, Request request)
337    {
338        SurveyRule ruleToExecute = null;
339        
340        Map<String, SurveyInputAnswer> answers = input.getAnswerMap();
341        
342        for (SurveyPage page : survey.getPages())
343        {
344            if (ruleToExecute == null || processPage(page, ruleToExecute))
345            {
346                // Reset the current rule.
347                ruleToExecute = null;
348                
349                AmetysObjectIterable<SurveyQuestion> questions = page.getQuestions();
350                
351                for (SurveyQuestion question : questions)
352                {
353                    SurveyInputAnswer answer = answers.get(question.getName());
354                    
355                    switch (question.getType())
356                    {
357                        case FREE_TEXT:
358                        case MULTILINE_FREE_TEXT:
359                            errors.addErrors(question.getName(), validateText(answer, request));
360                            ruleToExecute = evaluateTextRules(question, answer);
361                            break;
362                        case SINGLE_CHOICE:
363                        case MULTIPLE_CHOICE:
364                            errors.addErrors(question.getName(), validateChoice(answer, request));
365                            ruleToExecute = evaluateChoiceRules(question, answer);
366                            break;
367                        case SINGLE_MATRIX:
368                        case MULTIPLE_MATRIX:
369                            errors.addErrors(question.getName(), validateMatrix(answer, request));
370                            ruleToExecute = evaluateMatrixRules(question, answer);
371                            break;
372                        default:
373                            break;
374                    }
375                }
376                
377                SurveyRule pageRule = page.getRule();
378                
379                if (ruleToExecute == null && pageRule != null)
380                {
381                    ruleToExecute = pageRule;
382                }
383            }
384        }
385    }
386    
387    /**
388     * Test if a page is to be processed, depending on the rule.
389     * @param page the page to test.
390     * @param rule the rule to execute.
391     * @return true to process the page, false otherwise.
392     */
393    protected boolean processPage(SurveyPage page, SurveyRule rule)
394    {
395        boolean processPage = false;
396        
397        switch (rule.getType())
398        {
399            case JUMP:
400                // If the page is the targeted page, it passes the condition.
401                processPage = page.getId().equals(rule.getPage());
402                break;
403            case SKIP:
404                // If the page is the skipped page, it is not displayed.
405                processPage = !page.getId().equals(rule.getPage());
406                break;
407            case FINISH:
408                // When finished, no more page is displayed.
409                break;
410            default:
411                break;
412        }
413        
414        return processPage;
415    }
416    
417    /**
418     * Evaluate rules on a text question.
419     * @param question the text question.
420     * @param answer the user answer to the question.
421     * @return the matched rule.
422     */
423    protected SurveyRule evaluateTextRules(SurveyQuestion question, SurveyInputAnswer answer)
424    {
425        return null;
426    }
427    
428    /**
429     * Evaluate rules on a choice question.
430     * @param question the choice question.
431     * @param answer the user answer to the question.
432     * @return the matched rule.
433     */
434    protected SurveyRule evaluateChoiceRules(SurveyQuestion question, SurveyInputAnswer answer)
435    {
436        SurveyRule matchedRule = null;
437        
438        Map<String, Set<String>> valueMap = answer.getValuesMap();
439        if (valueMap.containsKey("values"))
440        {
441            Set<String> values = answer.getValuesMap().get("values");
442            
443            Iterator<SurveyRule> questionRules = question.getRules().iterator();
444            while (questionRules.hasNext() && matchedRule == null)
445            {
446                SurveyRule rule = questionRules.next();
447                
448                if (values.contains(rule.getOption()))
449                {
450                    // Condition met, store the action.
451                    matchedRule = rule;
452                }
453            }
454        }
455        
456        return matchedRule;
457    }
458    
459    /**
460     * Evaluate rules on a matrix question.
461     * @param question the matrix question.
462     * @param answer the user answer to the question.
463     * @return the matched rule.
464     */
465    protected SurveyRule evaluateMatrixRules(SurveyQuestion question, SurveyInputAnswer answer)
466    {
467        return null;
468    }
469    
470    /**
471     * Validate a text field.
472     * @param answer the user answer to the question.
473     * @param request the request.
474     * @return the error list.
475     */
476    protected List<I18nizableText> validateText(SurveyInputAnswer answer, Request request)
477    {
478        List<I18nizableText> errors = new ArrayList<>();
479        
480        SurveyQuestion question = answer.getQuestion();
481        
482        boolean isBlank = isBlank(answer);
483        
484        final String textPrefix = "PLUGINS_SURVEY_RENDER_ERROR_TEXT_";
485        
486        if (question.isMandatory() && isBlank)
487        {
488            errors.add(new I18nizableText("plugin." + _pluginName, textPrefix + "MANDATORY"));
489        }
490        
491        if (!isBlank)
492        {
493            errors.addAll(validatePattern(answer, textPrefix));
494        }
495        
496        return errors;
497    }
498    
499    /**
500     * Validate a choice question answer.
501     * @param answer the user answer to the question.
502     * @param request the request.
503     * @return the error list.
504     */
505    protected List<I18nizableText> validateChoice(SurveyInputAnswer answer, Request request)
506    {
507        List<I18nizableText> errors = new ArrayList<>();
508        
509        SurveyQuestion question = answer.getQuestion();
510        
511        boolean isBlank = isBlank(answer);
512        
513        final String textPrefix = "PLUGINS_SURVEY_RENDER_ERROR_CHOICE_";
514        
515        if (question.isMandatory() && isBlank)
516        {
517            errors.add(new I18nizableText("plugin." + _pluginName, textPrefix + "MANDATORY"));
518        }
519        
520        return errors;
521    }
522    
523    /**
524     * Validate a matrix question answer.
525     * @param answer the user answer to the question.
526     * @param request the request.
527     * @return the error list.
528     */
529    protected List<I18nizableText> validateMatrix(SurveyInputAnswer answer, Request request)
530    {
531        List<I18nizableText> errors = new ArrayList<>();
532        
533        SurveyQuestion question = answer.getQuestion();
534        
535        boolean isBlank = isBlank(answer);
536        
537        final String textPrefix = "PLUGINS_SURVEY_RENDER_ERROR_MATRIX_";
538        
539        if (question.isMandatory() && isBlank)
540        {
541            errors.add(new I18nizableText("plugin." + _pluginName, textPrefix + "MANDATORY"));
542        }
543        
544        return errors;
545    }
546    
547    /**
548     * Validate an answer against a pattern.
549     * @param answer the user answer to the question.
550     * @param keyPrefix the error i18n key prefix.
551     * @return the error list.
552     */
553    protected List<I18nizableText> validatePattern(SurveyInputAnswer answer, String keyPrefix)
554    {
555        List<I18nizableText> errors = new ArrayList<>();
556        
557        SurveyQuestion question = answer.getQuestion();
558        String regex = question.getRegExpPattern();
559        String value = answer.getValue();
560        
561        if (StringUtils.isNotBlank(regex))
562        {
563            if (!Pattern.matches(regex, value))
564            {
565                errors.add(new I18nizableText("plugin." + _pluginName, keyPrefix + "PATTERN"));
566            }
567        }
568        
569        return errors;
570    }
571    
572    /**
573     * Test if the answer is empty.
574     * @param answer the user answer.
575     * @return true if the answer is empty.
576     */
577    protected boolean isBlank(SurveyInputAnswer answer)
578    {
579        boolean blank = answer.getValuesMap().isEmpty();
580        
581        for (Set<String> values : answer.getValuesMap().values())
582        {
583            if (StringUtils.isEmpty(StringUtils.join(values, "")))
584            {
585                return true;
586            }
587        }
588        
589        return blank;
590    }
591    
592    /**
593     * Indicate in a cookie that the survey was taken.
594     * @param request the request.
595     * @param response the response.
596     * @param surveyId the ID of the survey that was just taken.
597     */
598    protected void setCookie(Request request, Response response, String surveyId)
599    {
600        Map<String, Cookie> cookieMap = request.getCookieMap();
601        
602        Cookie cookie = null;
603        String cookieValue = surveyId + "#" + SurveyDateUtils.zonedDateTimeToString(ZonedDateTime.now(ZoneId.of("UTC")));
604        if (cookieMap.containsKey(COOKIE_NAME))
605        {
606            cookie = cookieMap.get(COOKIE_NAME);
607            String newValue = cookie.getValue() + '|' + cookieValue;
608            cookie.setValue(newValue);
609        }
610        else
611        {
612            cookie = response.createCookie(COOKIE_NAME, cookieValue); 
613            cookie.setSecure(request.isSecure());
614            ((HttpCookie) cookie).getServletCookie().setHttpOnly(false); // Cookie is also manipulated by the javascript
615        }
616        
617        cookie.setVersion(1);
618        cookie.setMaxAge(30 * 24 * 3600); // 30 days
619        
620        String absPrefix = _prefixHandler.getAbsoluteUriPrefix();
621        
622        String host = null;
623        
624        try
625        {
626            URI frontUri = new URI(absPrefix);
627            host = frontUri.getHost();
628            String path = frontUri.getPath();
629            cookie.setPath(StringUtils.isEmpty(path) ? "/" : path);
630        }
631        catch (URISyntaxException e)
632        {
633            getLogger().warn("The front URI seems to be invalid.", e);
634        }
635        
636        if (StringUtils.isNotEmpty(host))
637        {
638            cookie.setDomain(host);
639            response.addCookie(cookie);
640        }
641    }
642    
643    /**
644     * Get a forwarded client IP address if available.
645     * @param request The servlet request object.
646     * @return The HTTP <code>X-Forwarded-For</code> header value if present,
647     * or the default remote address if not.
648     */
649    protected final String getClientIp(final Request request)
650    {
651        String ip = "";
652        
653        String forwardedIpHeader = request.getHeader("X-Forwarded-For");
654        
655        if (StringUtils.isNotEmpty(forwardedIpHeader))
656        {
657            String[] forwardedIps = forwardedIpHeader.split("[\\s,]+");
658            String forwardedIp = forwardedIps[forwardedIps.length - 1];
659            if (StringUtils.isNotBlank(forwardedIp))
660            {
661                ip = forwardedIp.trim();
662            }
663        }
664        
665        if (StringUtils.isBlank(ip))
666        {
667            ip = request.getRemoteAddr();
668        }
669        
670        return ip;
671    }
672    
673    /**
674     * Survey session with answers.
675     */
676    public class SurveyInput extends SurveySession
677    {
678        /** Answers. */
679        protected List<SurveyInputAnswer> _inputAnswers;
680        
681        @Override
682        public List<SurveyInputAnswer> getAnswers()
683        {
684            return _inputAnswers;
685        }
686        
687        /**
688         * Get the answers as a Map indexed by question ID.
689         * @return the answer Map.
690         */
691        public Map<String, SurveyInputAnswer> getAnswerMap()
692        {
693            Map<String, SurveyInputAnswer> answerMap = new LinkedHashMap<>();
694            
695            for (SurveyInputAnswer answer : _inputAnswers)
696            {
697                answerMap.put(answer.getQuestionId(), answer);
698            }
699            
700            return answerMap;
701        }
702        
703        /**
704         * Set the answers.
705         * @param answers the answers to set
706         */
707        public void setAnswerList(List<SurveyInputAnswer> answers)
708        {
709            this._inputAnswers = answers;
710        }
711    }
712    
713    /**
714     * Class representing a survey answer, i.e. the response of a user to a question of the survey.
715     */
716    public class SurveyInputAnswer extends SurveyAnswer
717    {
718        
719        /** The question. */
720        protected SurveyQuestion _question;
721        
722        /** The answer values. */
723        protected Map<String, Set<String>> _values;
724        
725        /**
726         * Build a SurveyAnswer object.
727         */
728        public SurveyInputAnswer()
729        {
730            this(null, null);
731        }
732        
733        /**
734         * Build a SurveyAnswer object.
735         * @param question the question ID.
736         * @param values the answer value.
737         */
738        public SurveyInputAnswer(SurveyQuestion question, Map<String, Set<String>> values)
739        {
740            this._question = question;
741            this._values = values;
742        }
743        
744        /**
745         * Get the question.
746         * @return the question
747         */
748        public SurveyQuestion getQuestion()
749        {
750            return _question;
751        }
752        
753        /**
754         * Set the question.
755         * @param question the question to set
756         */
757        public void setQuestion(SurveyQuestion question)
758        {
759            this._question = question;
760        }
761        
762        @Override
763        public String getQuestionId()
764        {
765            return _question.getName();
766        }
767        
768        @Override
769        public void setQuestionId(String questionId)
770        {
771            throw new IllegalAccessError("Set the question instead of the question ID.");
772        }
773        
774        /**
775         * Get the values.
776         * @return the values
777         */
778        public Map<String, Set<String>> getValuesMap()
779        {
780            return _values;
781        }
782        
783        /**
784         * Set the values.
785         * @param values the values to set
786         */
787        public void setValueMap(Map<String, Set<String>> values)
788        {
789            this._values = values;
790        }
791        
792        @Override
793        public String getValue()
794        {
795            String value = "";
796            
797            switch (_question.getType())
798            {
799                case SINGLE_MATRIX:
800                case MULTIPLE_MATRIX:
801                    StringBuilder valueBuff = new StringBuilder();
802                    
803                    for (String option : _values.keySet())
804                    {
805                        Set<String> values = _values.get(option);
806                        
807                        if (valueBuff.length() > 0)
808                        {
809                            valueBuff.append(";");
810                        }
811                        valueBuff.append(option).append(":").append(StringUtils.join(values, ","));
812                    }
813                    
814                    value = valueBuff.toString();
815                    break;
816                case FREE_TEXT:
817                case MULTILINE_FREE_TEXT:
818                case SINGLE_CHOICE:
819                case MULTIPLE_CHOICE:
820                default:
821                    value = StringUtils.defaultString(StringUtils.join(_values.get("values"), ","));
822                    break;
823            }
824            
825            return value;
826        }
827        
828        @Override
829        public void setValue(String value)
830        {
831            throw new IllegalAccessError("Set the value map instead of the vlaue.");
832        }
833        
834    }
835
836}