001package biweekly.component; 002 003import java.util.Arrays; 004import java.util.List; 005 006import biweekly.ICalVersion; 007import biweekly.Warning; 008import biweekly.parameter.Related; 009import biweekly.property.Action; 010import biweekly.property.Attachment; 011import biweekly.property.Attendee; 012import biweekly.property.DateDue; 013import biweekly.property.DateEnd; 014import biweekly.property.DateStart; 015import biweekly.property.Description; 016import biweekly.property.DurationProperty; 017import biweekly.property.Repeat; 018import biweekly.property.Summary; 019import biweekly.property.Trigger; 020import biweekly.util.Duration; 021 022/* 023 Copyright (c) 2013-2015, Michael Angstadt 024 All rights reserved. 025 026 Redistribution and use in source and binary forms, with or without 027 modification, are permitted provided that the following conditions are met: 028 029 1. Redistributions of source code must retain the above copyright notice, this 030 list of conditions and the following disclaimer. 031 2. Redistributions in binary form must reproduce the above copyright notice, 032 this list of conditions and the following disclaimer in the documentation 033 and/or other materials provided with the distribution. 034 035 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 036 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 037 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 038 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 039 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 040 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 041 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 042 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 043 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 044 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 045 */ 046 047/** 048 * <p> 049 * Defines a reminder for an event or to-do task. This class contains static 050 * factory methods to aid in the construction of valid alarms. 051 * </p> 052 * 053 * <p> 054 * <b>Examples:</b> 055 * 056 * <pre class="brush:java"> 057 * //audio alarm 058 * Trigger trigger = ... 059 * Attachment sound = ... 060 * VAlarm audio = VAlarm.audio(trigger, sound); 061 * 062 * //display alarm 063 * Trigger trigger = ... 064 * String message = "Meeting at 1pm"; 065 * VAlarm display = VAlarm.display(trigger, message); 066 * 067 * //email alarm 068 * Trigger trigger = ... 069 * String subject = "Reminder: Meeting at 1pm"; 070 * String body = "Team,\n\nThe team meeting scheduled for 1pm is about to start. Snacks will be served!\n\nThanks,\nJohn"; 071 * List<String> to = Arrays.asList("janedoe@example.com", "bobsmith@example.com"); 072 * VAlarm email = VAlarm.email(trigger, subject, body, to); 073 * </pre> 074 * 075 * </p> 076 * @author Michael Angstadt 077 * @see <a href="http://tools.ietf.org/html/rfc5545#page-71">RFC 5545 p.71-6</a> 078 * @see <a href="http://tools.ietf.org/html/rfc2445#page-67">RFC 2445 079 * p.67-73</a> 080 */ 081public class VAlarm extends ICalComponent { 082 /** 083 * Creates a new alarm. Consider using one of the static factory methods 084 * instead. 085 * @param action the alarm action (e.g. "email") 086 * @param trigger the trigger 087 */ 088 public VAlarm(Action action, Trigger trigger) { 089 setAction(action); 090 setTrigger(trigger); 091 } 092 093 /** 094 * Creates an audio alarm. 095 * @param trigger the trigger 096 * @return the alarm 097 */ 098 public static VAlarm audio(Trigger trigger) { 099 return audio(trigger, null); 100 } 101 102 /** 103 * Creates an audio alarm. 104 * @param trigger the trigger 105 * @param sound a sound to play when the alarm triggers 106 * @return the alarm 107 */ 108 public static VAlarm audio(Trigger trigger, Attachment sound) { 109 VAlarm alarm = new VAlarm(Action.audio(), trigger); 110 if (sound != null) { 111 alarm.addAttachment(sound); 112 } 113 return alarm; 114 } 115 116 /** 117 * Creates a display alarm. 118 * @param trigger the trigger 119 * @param displayText the display text 120 * @return the alarm 121 */ 122 public static VAlarm display(Trigger trigger, String displayText) { 123 VAlarm alarm = new VAlarm(Action.display(), trigger); 124 alarm.setDescription(displayText); 125 return alarm; 126 } 127 128 /** 129 * Creates an email alarm. 130 * @param trigger the trigger 131 * @param subject the email subject 132 * @param body the email body 133 * @param recipients the email address(es) to send the alert to 134 * @return the alarm 135 */ 136 public static VAlarm email(Trigger trigger, String subject, String body, String... recipients) { 137 return email(trigger, subject, body, Arrays.asList(recipients)); 138 } 139 140 /** 141 * Creates a procedure alarm (vCal 1.0 only). 142 * @param trigger the trigger 143 * @param path the path or name of the procedure 144 * @return the alarm 145 */ 146 public static VAlarm procedure(Trigger trigger, String path) { 147 VAlarm alarm = new VAlarm(Action.procedure(), trigger); 148 alarm.setDescription(path); 149 return alarm; 150 } 151 152 /** 153 * Creates an email alarm. 154 * @param trigger the trigger 155 * @param subject the email subject 156 * @param body the email body 157 * @param recipients the email address(es) to send the alert to 158 * @return the alarm 159 */ 160 public static VAlarm email(Trigger trigger, String subject, String body, List<String> recipients) { 161 VAlarm alarm = new VAlarm(Action.email(), trigger); 162 alarm.setSummary(subject); 163 alarm.setDescription(body); 164 for (String recipient : recipients) { 165 alarm.addAttendee(new Attendee(null, recipient)); 166 } 167 return alarm; 168 } 169 170 /** 171 * Gets any attachments that are associated with the alarm. 172 * @return the attachments 173 * @see <a href="http://tools.ietf.org/html/rfc5545#page-80">RFC 5545 174 * p.80-1</a> 175 */ 176 public List<Attachment> getAttachments() { 177 return getProperties(Attachment.class); 178 } 179 180 /** 181 * Adds an attachment to the alarm. Note that AUDIO alarms should only have 182 * 1 attachment. 183 * @param attachment the attachment to add 184 * @see <a href="http://tools.ietf.org/html/rfc5545#page-80">RFC 5545 185 * p.80-1</a> 186 */ 187 public void addAttachment(Attachment attachment) { 188 addProperty(attachment); 189 } 190 191 /** 192 * <p> 193 * Gets a detailed description of the alarm. The description should be more 194 * detailed than the one provided by the {@link Summary} property. 195 * </p> 196 * <p> 197 * This property has different meanings, depending on the alarm action: 198 * <ul> 199 * <li>DISPLAY - the display text</li> 200 * <li>EMAIL - the body of the email message</li> 201 * <li>all others - a general description of the alarm</li> 202 * </ul> 203 * </p> 204 * @return the description or null if not set 205 * @see <a href="http://tools.ietf.org/html/rfc5545#page-84">RFC 5545 206 * p.84-5</a> 207 */ 208 public Description getDescription() { 209 return getProperty(Description.class); 210 } 211 212 /** 213 * <p> 214 * Sets a detailed description of the alarm. The description should be more 215 * detailed than the one provided by the {@link Summary} property. 216 * </p> 217 * <p> 218 * This property has different meanings, depending on the alarm action: 219 * <ul> 220 * <li>DISPLAY - the display text</li> 221 * <li>EMAIL - the body of the email message</li> 222 * <li>all others - a general description of the alarm</li> 223 * </ul> 224 * </p> 225 * @param description the description or null to remove 226 * @see <a href="http://tools.ietf.org/html/rfc5545#page-84">RFC 5545 227 * p.84-5</a> 228 */ 229 public void setDescription(Description description) { 230 setProperty(Description.class, description); 231 } 232 233 /** 234 * <p> 235 * Sets a detailed description of the alarm. The description should be more 236 * detailed than the one provided by the {@link Summary} property. 237 * </p> 238 * <p> 239 * This property has different meanings, depending on the alarm action: 240 * <ul> 241 * <li>DISPLAY - the display text</li> 242 * <li>EMAIL - the body of the email message</li> 243 * <li>all others - a general description of the alarm</li> 244 * </ul> 245 * </p> 246 * @param description the description or null to remove 247 * @return the property that was created 248 * @see <a href="http://tools.ietf.org/html/rfc5545#page-84">RFC 5545 249 * p.84-5</a> 250 */ 251 public Description setDescription(String description) { 252 Description prop = (description == null) ? null : new Description(description); 253 setDescription(prop); 254 return prop; 255 } 256 257 /** 258 * <p> 259 * Gets the summary of the alarm. 260 * </p> 261 * <p> 262 * This property has different meanings, depending on the alarm action: 263 * <ul> 264 * <li>EMAIL - the subject line of the email</li> 265 * <li>all others - a one-line summary of the alarm</li> 266 * </ul> 267 * </p> 268 * @return the summary or null if not set 269 * @see <a href="http://tools.ietf.org/html/rfc5545#page-93">RFC 5545 270 * p.93-4</a> 271 */ 272 public Summary getSummary() { 273 return getProperty(Summary.class); 274 } 275 276 /** 277 * <p> 278 * Sets the summary of the alarm. 279 * </p> 280 * <p> 281 * This property has different meanings, depending on the alarm action: 282 * <ul> 283 * <li>EMAIL - the subject line of the email</li> 284 * <li>all others - a one-line summary of the alarm</li> 285 * </ul> 286 * </p> 287 * @param summary the summary or null to remove 288 * @see <a href="http://tools.ietf.org/html/rfc5545#page-93">RFC 5545 289 * p.93-4</a> 290 */ 291 public void setSummary(Summary summary) { 292 setProperty(Summary.class, summary); 293 } 294 295 /** 296 * <p> 297 * Sets the summary of the alarm. 298 * </p> 299 * <p> 300 * This property has different meanings, depending on the alarm action: 301 * <ul> 302 * <li>EMAIL - the subject line of the email</li> 303 * <li>all others - a one-line summary of the alarm</li> 304 * </ul> 305 * </p> 306 * @param summary the summary or null to remove 307 * @return the property that was created 308 * @see <a href="http://tools.ietf.org/html/rfc5545#page-93">RFC 5545 309 * p.93-4</a> 310 */ 311 public Summary setSummary(String summary) { 312 Summary prop = (summary == null) ? null : new Summary(summary); 313 setSummary(prop); 314 return prop; 315 } 316 317 /** 318 * Gets the people who will be emailed when the alarm fires (only applicable 319 * for EMAIL alarms). 320 * @return the email recipients 321 * @see <a href="http://tools.ietf.org/html/rfc5545#page-107">RFC 5545 322 * p.107-9</a> 323 */ 324 public List<Attendee> getAttendees() { 325 return getProperties(Attendee.class); 326 } 327 328 /** 329 * Adds a person who will be emailed when the alarm fires (only applicable 330 * for EMAIL alarms). 331 * @param attendee the email recipient 332 * @see <a href="http://tools.ietf.org/html/rfc5545#page-107">RFC 5545 333 * p.107-9</a> 334 */ 335 public void addAttendee(Attendee attendee) { 336 addProperty(attendee); 337 } 338 339 /** 340 * Gets the type of action to invoke when the alarm is triggered. 341 * @return the action or null if not set 342 * @see <a href="http://tools.ietf.org/html/rfc5545#page-132">RFC 5545 343 * p.132-3</a> 344 */ 345 public Action getAction() { 346 return getProperty(Action.class); 347 } 348 349 /** 350 * Sets the type of action to invoke when the alarm is triggered. 351 * @param action the action or null to remove 352 * @see <a href="http://tools.ietf.org/html/rfc5545#page-132">RFC 5545 353 * p.132-3</a> 354 */ 355 public void setAction(Action action) { 356 setProperty(Action.class, action); 357 } 358 359 /** 360 * Gets the length of the pause between alarm repetitions. 361 * @return the duration or null if not set 362 * @see <a href="http://tools.ietf.org/html/rfc5545#page-99">RFC 5545 363 * p.99</a> 364 */ 365 public DurationProperty getDuration() { 366 return getProperty(DurationProperty.class); 367 } 368 369 /** 370 * Sets the length of the pause between alarm repetitions. 371 * @param duration the duration or null to remove 372 * @see <a href="http://tools.ietf.org/html/rfc5545#page-99">RFC 5545 373 * p.99</a> 374 */ 375 public void setDuration(DurationProperty duration) { 376 setProperty(DurationProperty.class, duration); 377 } 378 379 /** 380 * Sets the length of the pause between alarm repetitions. 381 * @param duration the duration or null to remove 382 * @return the property that was created 383 * @see <a href="http://tools.ietf.org/html/rfc5545#page-99">RFC 5545 384 * p.99</a> 385 */ 386 public DurationProperty setDuration(Duration duration) { 387 DurationProperty prop = (duration == null) ? null : new DurationProperty(duration); 388 setDuration(prop); 389 return prop; 390 } 391 392 /** 393 * Gets the number of times an alarm should be repeated after its initial 394 * trigger. 395 * @return the repeat count or null if not set 396 * @see <a href="http://tools.ietf.org/html/rfc5545#page-133">RFC 5545 397 * p.133</a> 398 */ 399 public Repeat getRepeat() { 400 return getProperty(Repeat.class); 401 } 402 403 /** 404 * Sets the number of times an alarm should be repeated after its initial 405 * trigger. 406 * @param repeat the repeat count or null to remove 407 * @see <a href="http://tools.ietf.org/html/rfc5545#page-133">RFC 5545 408 * p.133</a> 409 */ 410 public void setRepeat(Repeat repeat) { 411 setProperty(Repeat.class, repeat); 412 } 413 414 /** 415 * Sets the number of times an alarm should be repeated after its initial 416 * trigger. 417 * @param count the repeat count (e.g. "2" to repeat it two more times after 418 * it was initially triggered, for a total of three times) or null to remove 419 * @return the property that was created 420 * @see <a href="http://tools.ietf.org/html/rfc5545#page-133">RFC 5545 421 * p.133</a> 422 */ 423 public Repeat setRepeat(Integer count) { 424 Repeat prop = (count == null) ? null : new Repeat(count); 425 setRepeat(prop); 426 return prop; 427 } 428 429 /** 430 * Sets the repetition information for the alarm. 431 * @param count the repeat count (e.g. "2" to repeat it two more times after 432 * it was initially triggered, for a total of three times) 433 * @param pauseDuration the length of the pause between repeats 434 * @see <a href="http://tools.ietf.org/html/rfc5545#page-133">RFC 5545 435 * p.133</a> 436 */ 437 public void setRepeat(int count, Duration pauseDuration) { 438 Repeat repeat = new Repeat(count); 439 DurationProperty duration = new DurationProperty(pauseDuration); 440 setRepeat(repeat); 441 setDuration(duration); 442 } 443 444 /** 445 * Gets when the alarm will be triggered. 446 * @return the trigger time or null if not set 447 * @see <a href="http://tools.ietf.org/html/rfc5545#page-133">RFC 5545 448 * p.133-6</a> 449 */ 450 public Trigger getTrigger() { 451 return getProperty(Trigger.class); 452 } 453 454 /** 455 * Sets when the alarm will be triggered. 456 * @param trigger the trigger time or null to remove 457 * @see <a href="http://tools.ietf.org/html/rfc5545#page-133">RFC 5545 458 * p.133-6</a> 459 */ 460 public void setTrigger(Trigger trigger) { 461 setProperty(Trigger.class, trigger); 462 } 463 464 @SuppressWarnings("unchecked") 465 @Override 466 protected void validate(List<ICalComponent> components, ICalVersion version, List<Warning> warnings) { 467 checkRequiredCardinality(warnings, Action.class, Trigger.class); 468 469 Action action = getAction(); 470 if (action != null) { 471 //AUDIO alarms should not have more than 1 attachment 472 if (action.isAudio()) { 473 if (getAttachments().size() > 1) { 474 warnings.add(Warning.validate(7)); 475 } 476 } 477 478 //DESCRIPTION is required for DISPLAY alarms 479 if (action.isDisplay()) { 480 checkRequiredCardinality(warnings, Description.class); 481 } 482 483 if (action.isEmail()) { 484 //SUMMARY and DESCRIPTION is required for EMAIL alarms 485 checkRequiredCardinality(warnings, Summary.class, Description.class); 486 487 //EMAIL alarms must have at least 1 ATTENDEE 488 if (getAttendees().isEmpty()) { 489 warnings.add(Warning.validate(8)); 490 } 491 } else { 492 //only EMAIL alarms can have ATTENDEEs 493 if (!getAttendees().isEmpty()) { 494 warnings.add(Warning.validate(9)); 495 } 496 } 497 498 if (action.isProcedure()) { 499 checkRequiredCardinality(warnings, Description.class); 500 } 501 } 502 503 Trigger trigger = getTrigger(); 504 if (trigger != null) { 505 Related related = trigger.getRelated(); 506 if (related != null) { 507 ICalComponent parent = components.get(components.size() - 1); 508 509 //if the TRIGGER is relative to DTSTART, confirm that DTSTART exists 510 if (related == Related.START && parent.getProperty(DateStart.class) == null) { 511 warnings.add(Warning.validate(11)); 512 } 513 514 //if the TRIGGER is relative to DTEND, confirm that DTEND (or DUE) exists 515 if (related == Related.END) { 516 boolean noEndDate = false; 517 518 if (parent instanceof VEvent) { 519 noEndDate = (parent.getProperty(DateEnd.class) == null && (parent.getProperty(DateStart.class) == null || parent.getProperty(DurationProperty.class) == null)); 520 } else if (parent instanceof VTodo) { 521 noEndDate = (parent.getProperty(DateDue.class) == null && (parent.getProperty(DateStart.class) == null || parent.getProperty(DurationProperty.class) == null)); 522 } 523 524 if (noEndDate) { 525 warnings.add(Warning.validate(12)); 526 } 527 } 528 } 529 } 530 } 531}