1 56 package org.opencrx.kernel.layer.application; 57 58 import java.io.BufferedReader ; 59 import java.io.ByteArrayInputStream ; 60 import java.io.ByteArrayOutputStream ; 61 import java.io.IOException ; 62 import java.io.InputStream ; 63 import java.io.InputStreamReader ; 64 import java.io.PrintWriter ; 65 import java.util.ArrayList ; 66 import java.util.Date ; 67 import java.util.HashMap ; 68 import java.util.Iterator ; 69 import java.util.List ; 70 import java.util.Map ; 71 import java.util.StringTokenizer ; 72 73 import org.openmdx.application.log.AppLog; 74 import org.openmdx.base.exception.ServiceException; 75 import org.openmdx.base.text.format.DateFormat; 76 import org.openmdx.compatibility.base.dataprovider.cci.AttributeSelectors; 77 import org.openmdx.compatibility.base.dataprovider.cci.AttributeSpecifier; 78 import org.openmdx.compatibility.base.dataprovider.cci.DataproviderObject; 79 import org.openmdx.compatibility.base.dataprovider.cci.DataproviderObject_1_0; 80 import org.openmdx.compatibility.base.dataprovider.cci.Directions; 81 import org.openmdx.compatibility.base.dataprovider.cci.RequestCollection; 82 import org.openmdx.compatibility.base.dataprovider.cci.SystemAttributes; 83 import org.openmdx.compatibility.base.naming.Path; 84 import org.openmdx.compatibility.base.query.FilterOperators; 85 import org.openmdx.compatibility.base.query.FilterProperty; 86 import org.openmdx.compatibility.base.query.Quantors; 87 import org.openmdx.kernel.exception.BasicException; 88 import org.openmdx.kernel.id.UUIDs; 89 90 public class ICalendar { 91 92 public ICalendar( 94 OpenCrxKernel_1 plugin, 95 RequestCollection delegation, 96 Codes codes 97 ) { 98 this.plugin = plugin; 99 this.delegation = delegation; 100 this.codes = codes; 101 } 102 103 private Short numberAsShort( 105 Object number 106 ) { 107 return new Short (((Number )number).shortValue()); 108 } 109 110 private String getPrimaryEMailAddress( 112 Path contact 113 ) throws ServiceException { 114 List addresses = this.delegation.addFindRequest( 115 contact.getChild("address"), 116 null, 117 AttributeSelectors.ALL_ATTRIBUTES, 118 0, 119 Integer.MAX_VALUE, 120 Directions.ASCENDING 121 ); 122 String emailAddress = null; 123 for(Iterator i = addresses.iterator(); i.hasNext(); ) { 124 DataproviderObject_1_0 address = (DataproviderObject_1_0)i.next(); 125 String addressClass = (String )address.values(SystemAttributes.OBJECT_CLASS).get(0); 126 List usage = new ArrayList (); 127 for(Iterator j = address.values("usage").iterator(); j.hasNext(); ) { 128 usage.add( 129 this.numberAsShort(j.next()) 130 ); 131 } 132 if("org:opencrx:kernel:account1:EMailAddress".equals(addressClass)) { 133 List adr = address.values("emailAddress"); 134 if((emailAddress == null) && (adr.size() > 0)) { 135 emailAddress = (String )adr.get(0); 136 } 137 if((adr.size() > 0) && (usage.contains(USAGE_EMAIL_PRIMARY))) { 138 emailAddress = (String )adr.get(0); 139 } 140 } 141 } 142 return emailAddress; 143 } 144 145 private String escapeNewlines( 147 String from 148 ) { 149 String to = ""; 150 for(int i = 0; i < from.length(); i++) { 151 if(from.charAt(i) == '\n') { 152 to += "\\n"; 153 } 154 else { 155 to += from.charAt(i); 156 } 157 } 158 return to; 159 } 160 161 public byte[] exportItem( 163 DataproviderObject_1_0 meeting, 164 short locale, 165 List statusMessage 166 ) throws ServiceException { 167 AppLog.trace("inspecting meeting", meeting); 168 169 String dtStart = null; 171 if(meeting.values("scheduledStart").size() == 0) { 172 statusMessage.add("DTSTART (scheduled start)"); 173 } 174 else { 175 dtStart = (String )meeting.values("scheduledStart").get(0); 176 } 177 String dtEnd = null; 179 if(meeting.values("scheduledEnd").size() == 0) { 180 statusMessage.add("DTEND (scheduled end)"); 181 } 182 else { 183 dtEnd = (String )meeting.values("scheduledEnd").get(0); 184 } 185 String priority = null; 187 if(meeting.values("priority").size() == 0) { 188 statusMessage.add("PRIORITY (priority)"); 189 } 190 else { 191 priority = meeting.values("priority").get(0).toString(); 192 } 193 String summary = meeting.values("name").size() == 0 ? "" : (String )meeting.values("name").get(0); 195 String description = meeting.values("description").size() == 0 ? "" : (String )meeting.values("description").get(0); 197 String location = meeting.values("location").size() == 0 ? "" : (String )meeting.values("location").get(0); 199 200 List participants = this.delegation.addFindRequest( 202 meeting.path().getChild("meetingParty"), 203 null, 204 AttributeSelectors.ALL_ATTRIBUTES, 205 0, 206 Integer.MAX_VALUE, 207 Directions.ASCENDING 208 ); 209 List attendees = new ArrayList (); 210 for(Iterator i = participants.iterator(); i.hasNext(); ) { 211 DataproviderObject_1_0 participant = (DataproviderObject_1_0)i.next(); 212 if(participant.values("party").size() > 0) { 213 try { 214 DataproviderObject_1_0 contact = this.delegation.addGetRequest( 215 (Path)participant.values("party").get(0), 216 AttributeSelectors.ALL_ATTRIBUTES, 217 new AttributeSpecifier[]{} 218 ); 219 String emailAddress = this.getPrimaryEMailAddress( 220 contact.path() 221 ); 222 String fullName = contact.values("fullName").size() == 0 ? null : (String )contact.values("fullName").get(0); 223 if(emailAddress != null) { 224 if(fullName == null) { 225 attendees.add( 226 "CN=" + emailAddress + ";ROLE=REQ-PARTICIPANT;RSVP=TRUE:MAILTO:" + emailAddress 227 ); 228 } 229 else { 230 attendees.add( 231 "CN=\"" + fullName + " (" + emailAddress + ")\";ROLE=REQ-PARTICIPANT;RSVP=TRUE:MAILTO:" + emailAddress 232 ); 233 } 234 } 235 } 236 catch(ServiceException e) { 237 if(e.getExceptionCode() != BasicException.Code.AUTHORIZATION_FAILURE) { 238 throw e; 239 } 240 } 241 } 242 } 243 244 String organizerEmailAddress = null; 246 if(meeting.values("assignedTo").size() == 0) { 247 statusMessage.add("ORGANIZER (assigned to)"); 248 } 249 else { 250 organizerEmailAddress = this.getPrimaryEMailAddress( 251 (Path)meeting.values("assignedTo").get(0) 252 ); 253 } 254 255 if(statusMessage.size() > 0) { 257 return null; 258 } 259 260 ByteArrayOutputStream os = new ByteArrayOutputStream (); 261 PrintWriter pw = new PrintWriter (os); 262 pw.println("BEGIN:VCALENDAR"); 263 pw.println("VERSION:2.0"); 264 pw.println("METHOD:PUBLISH"); 265 pw.println("BEGIN:VEVENT"); 266 for(int i = 0; i < attendees.size(); i++) { 267 pw.println("ATTENDEE;" + attendees.get(i)); 268 } 269 pw.println("ORGANIZER:MAILTO:" + (organizerEmailAddress == null ? "" : organizerEmailAddress)); 270 pw.println("DTSTART:" + dtStart.substring(0, 15) + "Z"); 271 pw.println("DTEND:" + dtEnd.substring(0, 15) + "Z"); 272 pw.println("LOCATION:" + location); 273 pw.println("TRANSP:OPAQUE"); 274 pw.println("SEQUENCE:0"); 275 pw.println("UID:" + (meeting.values("activityNumber").size() > 0 ? meeting.values("activityNumber").get(0) : meeting.path().getBase())); 276 pw.println("DTSTAMP:" + DateFormat.getInstance().format(new Date ())); 277 pw.println("DESCRIPTION:" + this.escapeNewlines(description)); 278 pw.println("SUMMARY:" + summary); 279 pw.println("PRIORITY:" + priority); 280 pw.println("CLASS:PUBLIC"); 281 pw.println("END:VEVENT"); 282 pw.println("END:VCALENDAR"); 283 try { 284 pw.flush(); 285 os.close(); 286 } catch(Exception e) {} 287 return os.toByteArray(); 288 } 289 290 public DataproviderObject importItem( 292 byte[] item, 293 Path activitySegmentPath, 294 Path contactSegmentPath, 295 short locale, 296 List errors, 297 List report 298 ) throws ServiceException { 299 try { 300 InputStream is = new ByteArrayInputStream (item); 301 BufferedReader reader = new BufferedReader (new InputStreamReader (is)); 302 Map data = new HashMap (); 303 String line = null; 304 int nAttendees = 0; 305 boolean lineCont = false; 306 String currentName = null; 307 while((line = reader.readLine()) != null) { 308 if(lineCont && line.startsWith(" ")) { 309 data.put( 310 currentName, 311 data.get(currentName) + line.substring(1) 312 ); 313 lineCont = false; 314 } 315 else if(line.startsWith("ATTENDEE;") || line.startsWith("attendee;")) { 316 currentName = "ATTENDEE[" + nAttendees + "]"; 317 data.put( 318 currentName, 319 line.substring("ATTENDEE;".length()) 320 ); 321 nAttendees++; 322 } 323 else if(line.indexOf(":") >= 0) { 324 currentName = line.substring(0, line.indexOf(":")).toUpperCase(); 325 data.put( 326 currentName, 327 line.substring(line.indexOf(":") + 1) 328 ); 329 lineCont = line.length() == 79; 330 } 331 } 332 AppLog.trace("ICalendar", data); 333 return this.importItem( 334 data, 335 activitySegmentPath, 336 contactSegmentPath, 337 locale, 338 errors, 339 report 340 ); 341 } 342 catch(IOException e) { 343 AppLog.warning("can not read item", e.getMessage()); 344 } 345 return null; 346 } 347 348 private DataproviderObject getAttendeeAsContact( 350 String attendeeAsString, 351 VCard vcardImporter, 352 Path contactSegmentPath, 353 short locale, 354 List report 355 ) { 356 Map vcard = new HashMap (); 357 int pos = attendeeAsString.indexOf("MAILTO:"); 358 String emailPrefInternet = attendeeAsString.substring(pos + 7); 359 vcard.put("EMAIL;PREF;INTERNET", emailPrefInternet); 360 String name = null; 361 if((attendeeAsString.startsWith("CN=\"") && (attendeeAsString.indexOf("(") >= 0))) { 362 name = attendeeAsString.substring(4, attendeeAsString.indexOf("(")); 363 } 364 else { 365 pos = emailPrefInternet.indexOf("@"); 366 name = emailPrefInternet.substring(0, pos > 0 ? pos : emailPrefInternet.length()); 367 name = name.replace('.', ' '); 368 } 369 StringBuffer n = new StringBuffer (); 370 StringTokenizer nameTokenizer = new StringTokenizer (name, " "); 371 while(nameTokenizer.hasMoreTokens()) { 372 n.insert(0, n.length() == 0 ? "" : ";"); 373 n.insert(0, nameTokenizer.nextToken()); 374 } 375 vcard.put("N", n.toString()); 376 try { 377 return vcardImporter.importItem( 378 vcard, 379 contactSegmentPath, 380 locale, 381 true, 382 report 383 ); 384 } 385 catch(Exception e) { 386 return null; 387 } 388 } 389 390 public DataproviderObject importItem( 392 Map data, 393 Path activitySegmentPath, 394 Path contactSegmentPath, 395 short locale, 396 List errors, 397 List report 398 ) throws ServiceException { 399 400 403 VCard vcardImporter = new VCard( 404 this.plugin, 405 this.delegation, 406 this.codes 407 ); 408 List attendees = new ArrayList (); 409 while(data.get("ATTENDEE[" + attendees.size() + "]") != null) { 410 String attendeeAsString = (String )data.get("ATTENDEE[" + attendees.size() + "]"); 411 if(attendeeAsString.indexOf("MAILTO:") < 0) { 412 errors.add("MAILTO (" + attendeeAsString + ")"); 413 } 414 else { 415 DataproviderObject attendee = this.getAttendeeAsContact( 416 attendeeAsString, 417 vcardImporter, 418 contactSegmentPath, 419 locale, 420 report 421 ); 422 if(attendee != null) { 423 attendees.add(attendee); 424 } 425 } 426 } 427 if(errors.size() > 0) { 428 return null; 429 } 430 AppLog.trace("attendees=", attendees); 431 432 435 DataproviderObject meeting = null; 436 437 try { 439 meeting = this.plugin.retrieveObjectForModification( 440 activitySegmentPath.getDescendant(new String []{"activity", data.get("UID") + ""}) 441 ); 442 } 443 catch(ServiceException e) { 445 if(e.getExceptionCode() == BasicException.Code.NOT_FOUND) { 446 List meetings = this.delegation.addFindRequest( 447 activitySegmentPath.getChild("activity"), 448 new FilterProperty[]{ 449 new FilterProperty( 450 Quantors.THERE_EXISTS, 451 "activityNumber", 452 FilterOperators.IS_IN, 453 new String []{data.get("UID") + ""} 454 ) 455 }, 456 AttributeSelectors.ALL_ATTRIBUTES, 457 0, 458 Integer.MAX_VALUE, 459 Directions.ASCENDING 460 ); 461 if(meetings.size() > 0) { 462 meeting = this.plugin.retrieveObjectForModification( 463 ((DataproviderObject)meetings.iterator().next()).path() 464 ); 465 } 466 } 467 } 468 if(meeting == null) { 469 String meetingNumber = (String )data.get("UID"); 470 if((meetingNumber == null) || (meetingNumber.length() == 0)) { 471 UUIDs.getGenerator().next().toString(); 472 } 473 meeting = new DataproviderObject( 474 activitySegmentPath.getDescendant( 475 new String []{ 476 "activity", 477 UUIDs.getGenerator().next().toString() 478 } 479 ) 480 ); 481 meeting.values(SystemAttributes.OBJECT_CLASS).add("org:opencrx:kernel:activity1:Meeting"); 482 meeting.values("activityState").add(new Short ((short)0)); 483 meeting.values("activityNumber").add(meetingNumber); 484 this.delegation.addCreateRequest( 485 meeting 486 ); 487 meeting = this.plugin.retrieveObjectForModification( 488 meeting.path() 489 ); 490 report.add("Create meeting"); 491 } 492 String s = (String )data.get("DTSTART"); 493 if((s != null) && (s.length() > 0)) { 494 try { 495 meeting.clearValues("scheduledStart").add( 496 DateFormat.getInstance().format(DateFormat.getInstance().parse( 497 s.substring(0, s.length()-1) + ".000Z") 498 ) 499 ); 500 } catch(Exception e) { 501 errors.add("DTSTART (" + s + ")"); 502 } 503 } 504 s = (String )data.get("DTEND"); 505 if((s != null) && (s.length() > 0)) { 506 try { 507 meeting.clearValues("scheduledEnd").add( 508 DateFormat.getInstance().format(DateFormat.getInstance().parse( 509 s.substring(0, s.length()-1) + ".000Z") 510 ) 511 ); 512 } catch(Exception e) { 513 errors.add("DTEND (" + s + ")"); 514 } 515 } 516 s = (String )data.get("PRIORITY"); 517 if((s != null) && (s.length() > 0)) { 518 try { 519 meeting.clearValues("priority").add(new Short (s)); 520 } catch(Exception e) { 521 errors.add("PRIORITY (" + s + ")"); 522 } 523 } 524 s = (String )data.get("SUMMARY"); 525 if((s != null) && (s.length() > 0)) { 526 meeting.clearValues("name").add(s); 527 } 528 s = (String )data.get("DESCRIPTION"); 529 if((s != null) && (s.length() > 0)) { 530 String temp = ""; 531 int pos = 0; 532 while((pos = s.indexOf("\\n")) >= 0) { 533 temp += temp.length() == 0 ? "" : "\n"; 534 temp += s.substring(0, pos); 535 s = s.substring(pos + 2); 536 } 537 temp += temp.length() == 0 ? "" : "\n"; 538 temp += s; 539 meeting.clearValues("description").add(temp); 540 } 541 s = (String )data.get("ORGANIZER"); 542 if((s != null) && (s.length() > 0)) { 543 DataproviderObject organizer = this.getAttendeeAsContact( 544 s.startsWith("MAILTO:") ? s : "MAILTO:" + s, 545 vcardImporter, 546 contactSegmentPath, 547 locale, 548 report 549 ); 550 if(organizer != null) { 551 meeting.clearValues("assignedTo").add( 552 organizer.path() 553 ); 554 } 555 } 556 if(errors.size() > 0) { 557 return null; 558 } 559 report.add("Update meeting"); 560 561 564 List meetingParties = this.delegation.addFindRequest( 565 meeting.path().getChild("meetingParty"), 566 null, 567 AttributeSelectors.ALL_ATTRIBUTES, 568 0, 569 Integer.MAX_VALUE, 570 Directions.ASCENDING 571 ); 572 int count = 0; 574 List meetingPartiesToBeRemoved = new ArrayList (); 575 for( 576 Iterator i = meetingParties.iterator(); 577 i.hasNext(); 578 count++ 579 ) { 580 if(count > attendees.size()) { 581 meetingPartiesToBeRemoved.add( 582 ((DataproviderObject_1_0)i.next()).path() 583 ); 584 } 585 else { 586 DataproviderObject attendee = (DataproviderObject)attendees.get(count); 587 DataproviderObject meetingParty = this.plugin.retrieveObjectForModification( 588 ((DataproviderObject_1_0)i.next()).path() 589 ); 590 meetingParty.clearValues("partyType").add(new Short ((short)0)); 591 meetingParty.clearValues("party").add(attendee.path()); 592 report.add("Update meeting party"); 593 } 594 } 595 for(int i = count; i < attendees.size(); i++) { 597 DataproviderObject attendee = (DataproviderObject)attendees.get(i); 598 DataproviderObject meetingParty = new DataproviderObject( 599 meeting.path().getDescendant(new String []{"meetingParty", UUIDs.getGenerator().next().toString()}) 600 ); 601 meetingParty.values(SystemAttributes.OBJECT_CLASS).add("org:opencrx:kernel:activity1:MeetingParty"); 602 meetingParty.clearValues("partyType").add(new Short ((short)0)); 603 meetingParty.clearValues("party").add(attendee.path()); 604 this.delegation.addCreateRequest( 605 meetingParty 606 ); 607 report.add("create meeting party"); 608 } 609 for(Iterator i = meetingPartiesToBeRemoved.iterator(); i.hasNext(); ) { 611 this.delegation.addRemoveRequest( 612 (Path)i.next() 613 ); 614 } 615 return meeting; 616 } 617 618 public static final String MIME_TYPE = "text/calendar"; 622 public static final int MIME_TYPE_CODE = 4; 623 624 static private final Short USAGE_EMAIL_PRIMARY = new Short ((short)300); 625 626 private final OpenCrxKernel_1 plugin; 627 private final RequestCollection delegation; 628 private final Codes codes; 629 } 630 631 633 | Popular Tags |