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}