/* * � Copyright IBM Corp. 2012 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. See the License for the specific language governing * permissions and limitations under the License. */ package com.ibm.domino.calendar.dbstore; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Vector; import java.util.regex.Matcher; import java.util.regex.Pattern; import lotus.domino.Database; import lotus.domino.DateTime; import lotus.domino.NotesCalendar; import lotus.domino.NotesCalendarEntry; import lotus.domino.NotesCalendarNotice; import lotus.domino.NotesError; import lotus.domino.NotesException; import lotus.domino.Session; import com.ibm.domino.calendar.store.Action; import com.ibm.domino.calendar.store.CounterAction; import com.ibm.domino.calendar.store.DeclineAction; import com.ibm.domino.calendar.store.DelegateAction; import com.ibm.domino.calendar.store.EventSet; import com.ibm.domino.calendar.store.ICalendarStore; import com.ibm.domino.calendar.store.RecurrenceRange; import com.ibm.domino.calendar.store.StoreException; import com.ibm.domino.commons.util.BackendUtil; /** * Event store that uses the Notes Java back-end classes. */ public class NotesCalendarStore implements ICalendarStore { public static final String RANGE_FIELDS_FILTER_CATEGORIES = "categories"; // $NON-NLS-1$ public static final String RANGE_FIELDS_FILTER_CLASS = "class"; // $NON-NLS-1$ public static final String RANGE_FIELDS_FILTER_END = "end"; // $NON-NLS-1$ public static final String RANGE_FIELDS_FILTER_ID = "id"; // $NON-NLS-1$ public static final String RANGE_FIELDS_FILTER_LOCATION = "location"; // $NON-NLS-1$ public static final String RANGE_FIELDS_FILTER_PRIORITY = "priority"; // $NON-NLS-1$ public static final String RANGE_FIELDS_FILTER_RECURRENCEID = "reccurenceid"; // $NON-NLS-1$ public static final String RANGE_FIELDS_FILTER_SEQUENCE = "sequence"; // $NON-NLS-1$ public static final String RANGE_FIELDS_FILTER_START = "start"; // $NON-NLS-1$ public static final String RANGE_FIELDS_FILTER_STATUS = "status"; // $NON-NLS-1$ public static final String RANGE_FIELDS_FILTER_SUMMARY = "summary"; // $NON-NLS-1$ public static final String RANGE_FIELDS_FILTER_TRANSPARENCY = "transparency"; // $NON-NLS-1$ public static final String RANGE_FIELDS_FILTER_URL = "url"; // $NON-NLS-1$ public static final String RANGE_FIELDS_FILTER_X_LOTUS_APPTYPE = "x-lotus-apptype"; // $NON-NLS-1$ public static final String RANGE_FIELDS_FILTER_X_LOTUS_NOTICETYPE = "x-lotus-noticetype"; // $NON-NLS-1$ public static final String RANGE_FIELDS_FILTER_X_LOTUS_ONLINEMEETING_URL = "x-lotus-onlinemeeting-url"; // $NON-NLS-1$ public static final String RANGE_FIELDS_FILTER_X_LOTUS_ORGANIZER = "x-lotus-organizer"; // $NON-NLS-1$ public static final String RANGE_FIELDS_FILTER_X_LOTUS_ROOM = "x-lotus-room"; // $NON-NLS-1$ private static final long ONE_YEAR = 365L * 24 * 60 * 60 * 1000; private static final int READ_RANGE_ALL = 0xFFFFFFFF; private static Map<String,Integer> SUPPORT_RANGE_FILTER = new HashMap<String,Integer>(); static{ SUPPORT_RANGE_FILTER.put(RANGE_FIELDS_FILTER_CATEGORIES,new Integer(NotesCalendar.CS_READ_RANGE_MASK_CATEGORY)); SUPPORT_RANGE_FILTER.put(RANGE_FIELDS_FILTER_CLASS,new Integer(NotesCalendar.CS_READ_RANGE_MASK_CLASS)); SUPPORT_RANGE_FILTER.put(RANGE_FIELDS_FILTER_END,new Integer(NotesCalendar.CS_READ_RANGE_MASK_DTEND)); SUPPORT_RANGE_FILTER.put(RANGE_FIELDS_FILTER_LOCATION,new Integer(NotesCalendar.CS_READ_RANGE_MASK_LOCATION)); SUPPORT_RANGE_FILTER.put(RANGE_FIELDS_FILTER_PRIORITY,new Integer(NotesCalendar.CS_READ_RANGE_MASK_PRIORITY)); SUPPORT_RANGE_FILTER.put(RANGE_FIELDS_FILTER_RECURRENCEID,new Integer(NotesCalendar.CS_READ_RANGE_MASK_RECURRENCE_ID)); SUPPORT_RANGE_FILTER.put(RANGE_FIELDS_FILTER_SEQUENCE,new Integer(NotesCalendar.CS_READ_RANGE_MASK_SEQUENCE)); SUPPORT_RANGE_FILTER.put(RANGE_FIELDS_FILTER_START,new Integer(NotesCalendar.CS_READ_RANGE_MASK_DTSTART)); SUPPORT_RANGE_FILTER.put(RANGE_FIELDS_FILTER_STATUS,new Integer(NotesCalendar.CS_READ_RANGE_MASK_STATUS)); SUPPORT_RANGE_FILTER.put(RANGE_FIELDS_FILTER_SUMMARY,new Integer(NotesCalendar.CS_READ_RANGE_MASK_SUMMARY)); SUPPORT_RANGE_FILTER.put(RANGE_FIELDS_FILTER_TRANSPARENCY,new Integer(NotesCalendar.CS_READ_RANGE_MASK_TRANSP)); SUPPORT_RANGE_FILTER.put(RANGE_FIELDS_FILTER_X_LOTUS_APPTYPE,new Integer(NotesCalendar.CS_READ_RANGE_MASK_APPTTYPE)); SUPPORT_RANGE_FILTER.put(RANGE_FIELDS_FILTER_X_LOTUS_NOTICETYPE,new Integer(NotesCalendar.CS_READ_RANGE_MASK_NOTICETYPE)); SUPPORT_RANGE_FILTER.put(RANGE_FIELDS_FILTER_X_LOTUS_ONLINEMEETING_URL,new Integer(NotesCalendar.CS_READ_RANGE_MASK_ONLINE_URL)); SUPPORT_RANGE_FILTER.put(RANGE_FIELDS_FILTER_X_LOTUS_ORGANIZER,new Integer(NotesCalendar.CS_READ_RANGE_MASK_NOTESORGANIZER)); SUPPORT_RANGE_FILTER.put(RANGE_FIELDS_FILTER_X_LOTUS_ROOM,new Integer(NotesCalendar.CS_READ_RANGE_MASK_NOTESROOM)); } private Database _database; public NotesCalendarStore(Database database) { _database = database; } public EventSet getEvents(Date fromDate, Date toDate, int skipCount, int maxEvents, ArrayList<String> rangeFieldsFilter) throws StoreException { EventSet result = null; NotesCalendar calendar = null; DateTime dtstart = null; DateTime dtend = null; try { Session session = _database.getParent(); calendar = session.getCalendar(_database); // TODO: Should we set the mask back to the default when we are done? int newMask = 0; boolean useDefaultFilterMask = true; if( rangeFieldsFilter!=null ){ Iterator<String> rangeFieldsFilterIterator = rangeFieldsFilter.iterator(); while(rangeFieldsFilterIterator.hasNext()){ String filter = rangeFieldsFilterIterator.next(); if(SUPPORT_RANGE_FILTER.containsKey(filter)){ newMask |= SUPPORT_RANGE_FILTER.get(filter); useDefaultFilterMask = false; } } } if(useDefaultFilterMask){ newMask = READ_RANGE_ALL; } // Convert Java dates to DateTime objects if ( fromDate == null ) { if ( toDate == null ) { // Start and end dates are null. Read one year of // events starting today. Date now = new Date(); dtstart = session.createDateTime(now); dtend = session.createDateTime(new Date(now.getTime() + ONE_YEAR)); } else { // Start date is null, but end date is not. Read // one year of events ending on the given date. dtstart = session.createDateTime(new Date(toDate.getTime() - ONE_YEAR)); dtend = session.createDateTime(toDate); } } else { if ( toDate == null ) { // End date is null, but start date is not. Read // one year of events starting on the given date. dtstart = session.createDateTime(fromDate); dtend = session.createDateTime( new Date(fromDate.getTime() + ONE_YEAR) ); } else { dtstart = session.createDateTime(fromDate); dtend = session.createDateTime(toDate); } } calendar.setReadRangeMask1(newMask); String events = calendar.readRange(dtstart, dtend, skipCount, maxEvents); result = new EventSet(events, calendar.getEntriesProcessed() - skipCount); } catch (NotesException e) { throw new StoreException("Error reading events", mapError(e.id), e); // $NLX-NotesCalendarStore.Errorreadingevents-1$ } finally { BackendUtil.safeRecycle(dtstart); BackendUtil.safeRecycle(dtend); BackendUtil.safeRecycle(calendar); } return result; } public String getEvent(String id, String recurrenceId) throws StoreException { String result = null; NotesCalendar calendar = null; try { Session session = _database.getParent(); calendar = session.getCalendar(_database); NotesCalendarEntry entry = calendar.getEntry(id); result = entry.read(recurrenceId); } catch (NotesException e) { throw new StoreException("Error reading event", mapError(e.id), e); // $NLX-NotesEventStore.Errorreadingevent-1$ } finally { BackendUtil.safeRecycle(calendar); } return result; } public String createEvent(String event, int flags) throws StoreException { String result = null; NotesCalendar calendar = null; try { Session session = _database.getParent(); calendar = session.getCalendar(_database); NotesCalendarEntry entry = calendar.createEntry(event, translateFlags(flags)); result = entry.read(); } catch (NotesException e) { throw new StoreException("Error creating event", mapError(e.id), e); // $NLX-NotesEventStore.Errorcreatingevent-1$ } finally { BackendUtil.safeRecycle(calendar); } return result; } public String updateEvent(String event, String id, String recurrenceId, String comments, int flags) throws StoreException { String result = null; NotesCalendar calendar = null; try { Session session = _database.getParent(); calendar = session.getCalendar(_database); NotesCalendarEntry entry = calendar.getEntry(id); entry.update(event, comments, translateFlags(flags), recurrenceId); result = entry.read(recurrenceId); } catch (NotesException e) { throw new StoreException("Error updating event", mapError(e.id), e); // $NLX-NotesEventStore.Errorupdatingevent-1$ } finally { BackendUtil.safeRecycle(calendar); } return result; } public void deleteEvent(String id, String recurrenceId, RecurrenceRange range, int flags) throws StoreException { NotesCalendar calendar = null; try { Session session = _database.getParent(); calendar = session.getCalendar(_database); NotesCalendarEntry entry = calendar.getEntry(id); if ( (flags & ICalendarStore.FLAG_NO_WORKFLOW) != 0 ) { calendar.setAutoSendNotices(false); } if ( recurrenceId == null ) { entry.remove(NotesCalendarEntry.CS_RANGE_REPEAT_ALL, null); } else { entry.remove(rangeToInt(range), recurrenceId); } } catch (NotesException e) { throw new StoreException("Error deleting event", mapError(e.id), e); // $NLX-NotesEventStore.Errordeletingevent-1$ } finally { BackendUtil.safeRecycle(calendar); } } public void processEventAction(String id, String recurrenceId, RecurrenceRange range, Action action) throws StoreException { NotesCalendar calendar = null; try { Session session = _database.getParent(); calendar = session.getCalendar(_database); NotesCalendarEntry entry = calendar.getEntry(id); boolean keepInformed = false; switch(action.getActionType()) { case Action.ACTION_ACCEPT: entry.accept(action.getComments(), rangeToInt(range), recurrenceId); break; case Action.ACTION_CANCEL: entry.cancel(action.getComments(), rangeToInt(range), recurrenceId); break; case Action.ACTION_COUNTER: CounterAction ca = (CounterAction)action; DateTime start = session.createDateTime(ca.getStart()); DateTime end = session.createDateTime(ca.getEnd()); entry.counter(action.getComments(), start, end, rangeToInt(range), recurrenceId); break; case Action.ACTION_DECLINE: DeclineAction decline = (DeclineAction)action; if ( decline.getKeepInformed() != null ) { // This logic is not exactly right. We need a way to tell decline() // to use the user's default value for keepInformed. TODO: Fix // this when we have the right variant of decline(). keepInformed = decline.getKeepInformed().booleanValue(); } entry.decline(action.getComments(), keepInformed, rangeToInt(range), recurrenceId); break; case Action.ACTION_DELEGATE: DelegateAction delegate = (DelegateAction)action; if ( delegate.getKeepInformed() != null ) { // This logic is not exactly right. We need a way to tell delegate() // to use the user's default value for keepInformed. TODO: Fix // this when we have the right variant of delegate(). keepInformed = delegate.getKeepInformed().booleanValue(); } entry.delegate(delegate.getComments(), delegate.getDelegateTo(), keepInformed, rangeToInt(range), recurrenceId); break; case Action.ACTION_DELETE: entry.remove(rangeToInt(range),recurrenceId); break; case Action.ACTION_REMOVE_CANCEL: // TODO: What does this map to? break; case Action.ACTION_REQUEST_INFO: entry.requestInfo(action.getComments(),recurrenceId); break; case Action.ACTION_TENTATIVE: entry.tentativelyAccept(action.getComments(), rangeToInt(range), recurrenceId); break; case Action.ACTION_PROCESS_ALL: // TODO: Figure what this maps to. The processAll method is no longer // exported from the back end. //entry.processAll(); break; default: throw new StoreException("Error processing event action", StoreException.ERR_BAD_ACTION,new NotesException()); // $NLX-NotesCalendarStore.Errorprocessingeventaction-1$ } } catch (NotesException e) { throw new StoreException("Error processing event action", mapError(e.id), e); // $NLX-NotesEventStore.Errorprocessingeventaction-1$ } finally { BackendUtil.safeRecycle(calendar); } } public String[] getUnappliedNotices(String id) throws StoreException { String iCalendarNotices[] = null; NotesCalendar calendar = null; try { Session session = _database.getParent(); calendar = session.getCalendar(_database); Vector<Object> list = null; NotesCalendarEntry entry = calendar.getEntry(id); if ( entry != null ) { list = entry.getNotices(); } // Convert Vector<NotesCalendarNotice> to an array of iCalendar notices. if ( list != null && list.size() > 0 ) { iCalendarNotices = new String[list.size()]; Iterator<Object> iterator = list.iterator(); int i = 0; while (iterator.hasNext()) { Object obj = iterator.next(); if ( obj instanceof NotesCalendarNotice) { NotesCalendarNotice notice = (NotesCalendarNotice)obj; iCalendarNotices[i++] = notice.read(); } } } } catch (NotesException e) { throw new StoreException("Error getting unapplied notices", mapError(e.id), e); // $NLX-NotesEventStore.Errorgettingunappliednotices-1$ } finally { BackendUtil.safeRecycle(calendar); } return iCalendarNotices; } public String[] getNewInvitations(Date start, Date since, String id) throws StoreException { String iCalendarNotice[] = null; NotesCalendar calendar = null; DateTime dtStart = null; DateTime dtSince = null; try { Session session = _database.getParent(); calendar = session.getCalendar(_database); if (start != null) { dtStart = session.createDateTime(start); } if ( since != null ) { dtSince = session.createDateTime(since); } // Get a list of new invitations List<String> iCalendarList = null; Vector<Object> list = calendar.getNewInvitations(dtStart, dtSince); // Convert Vector<NotesCalendarNotice> to an array of iCalendar notices. if ( list != null && list.size() > 0 ) { // Convert the list of NotesCalendarNotice objects to a list of strings iCalendarList = new ArrayList<String>(); Iterator<Object> noticeIterator = list.iterator(); boolean matched = false; StringBuffer noticeValue = new StringBuffer(); while (noticeIterator.hasNext()) { Object obj = noticeIterator.next(); if ( obj instanceof NotesCalendarNotice) { matched = false; noticeValue.setLength(0); NotesCalendarNotice notice = (NotesCalendarNotice)obj; try { // The read is wrapped in it's own try block so one failure // doesn't sink the whole list noticeValue.append(notice.read()); if(id == null){ matched = true; } else{ Pattern patternUID = Pattern.compile("(\n|\r)"+"UID:"+id+"(\n|\r)"); // $NON-NLS-1$ $NON-NLS-2$ $NON-NLS-3$ Matcher matcher = patternUID.matcher(noticeValue); matched = matcher.find(); } if( matched ){ iCalendarList.add(noticeValue.toString()); } } catch (NotesException e) { // TODO: Log a warning } } } } if ( iCalendarList != null && iCalendarList.size() > 0 ) { // Convert the list of strings to an array iCalendarNotice = new String[iCalendarList.size()]; Iterator<String> iterator = iCalendarList.iterator(); int i = 0; while (iterator.hasNext()) { iCalendarNotice[i++] = iterator.next(); } } } catch (NotesException e) { throw new StoreException("Error getting new invitations", mapError(e.id), e); // $NLX-NotesEventStore.Errorgettingnewinvitations-1$ } finally { BackendUtil.safeRecycle(dtStart); BackendUtil.safeRecycle(dtSince); BackendUtil.safeRecycle(calendar); } return iCalendarNotice; } public String getNotice(String id) throws StoreException { String result = null; NotesCalendar calendar = null; try { Session session = _database.getParent(); calendar = session.getCalendar(_database); NotesCalendarNotice notice = null; notice = calendar.getNoticeByUNID(id); if ( notice != null ) { result = notice.read(); } } catch (NotesException e) { throw new StoreException("Error getting calendar notice", mapError(e.id), e); // $NLX-NotesEventStore.Errorgettingnotice-1$ } finally { BackendUtil.safeRecycle(calendar); } return result; } public void processNoticeAction(String id, Action action) throws StoreException { NotesCalendar calendar = null; try { Session session = _database.getParent(); calendar = session.getCalendar(_database); NotesCalendarNotice notice = calendar.getNoticeByUNID(id); boolean keepInformed = false; switch(action.getActionType()) { case Action.ACTION_ACCEPT: notice.accept(action.getComments()); break; case Action.ACTION_COUNTER: CounterAction ca = (CounterAction)action; DateTime start = session.createDateTime(ca.getStart()); DateTime end = session.createDateTime(ca.getEnd()); notice.counter(action.getComments(), start, end); break; case Action.ACTION_DECLINE: DeclineAction decline = (DeclineAction)action; if ( decline.getKeepInformed() != null ) { // This logic is not exactly right. We need a way to tell decline() // to use the user's default value for keepInformed. TODO: Fix // this when we have the right variant of decline(). keepInformed = decline.getKeepInformed().booleanValue(); } // Without keepInformed equal keepInformed false notice.decline(action.getComments(), keepInformed); break; case Action.ACTION_DELEGATE: DelegateAction delegate = (DelegateAction)action; if ( delegate.getKeepInformed() != null ) { // This logic is not exactly right. We need a way to tell delegate() // to use the user's default value for keepInformed. TODO: Fix // this when we have the right variant of delegate(). keepInformed = delegate.getKeepInformed().booleanValue(); } notice.delegate(delegate.getComments(), delegate.getDelegateTo(), keepInformed); break; case Action.ACTION_REMOVE_CANCEL: notice.removeCancelled(); break; case Action.ACTION_REQUEST_INFO: notice.requestInfo(action.getComments()); break; case Action.ACTION_TENTATIVE: notice.tentativelyAccept(action.getComments()); break; default: throw new StoreException("Error processing notice action", StoreException.ERR_ACTION_NOT_SUPPORTED,new NotesException()); // $NLX-NotesCalendarStore.Errorprocessingnoticeaction-1$ } } catch (NotesException e) { throw new StoreException("Error processing notice action", mapError(e.id), e); // $NLX-NotesEventStore.Errorprocessingnoticeaction-1$ } finally { BackendUtil.safeRecycle(calendar); } } private int rangeToInt(RecurrenceRange range) { int value = NotesCalendarEntry.CS_RANGE_REPEAT_CURRENT; if ( range != null ) { switch(range) { case THIS_AND_PREVIOUS: value = NotesCalendarEntry.CS_RANGE_REPEAT_PREV; break; case THIS_AND_FUTURE: value = NotesCalendarEntry.CS_RANGE_REPEAT_FUTURE; break; case ALL_INSTANCES: value = NotesCalendarEntry.CS_RANGE_REPEAT_ALL; break; } } return value; } private int translateFlags(int storeFlags) { int flags = 0; if ( (storeFlags & FLAG_NO_WORKFLOW) != 0 ) { flags |= NotesCalendar.CS_WRITE_DISABLE_IMPLICIT_SCHEDULING; } if ( (storeFlags & FLAG_REPLACE_COMPLETELY) != 0 ) { flags |= NotesCalendar.CS_WRITE_MODIFY_LITERAL; } if ( (storeFlags & FLAG_SMART_SEQUENCE) != 0 ) { flags |= NotesCalendar.CS_WRITE_SMARTSEQUENCE; } return flags; } private int mapError(int notesError) { int storeError = StoreException.ERR_INTERNAL; switch(notesError) { case NotesError.NOTES_ERR_NOTESCALENDAR_ERROR: // TODO: Fix this. Temporarily mapping to the wrong error so // calendar service unit tests will pass. storeError = StoreException.ERR_ACTION_NOT_SUPPORTED; break; case NotesError.NOTES_ERR_INVALIDID: storeError = StoreException.ERR_BAD_IDENTIFIER; break; case NotesError.NOTES_ERR_ERRSENDINGNOTICES: storeError = StoreException.ERR_SENDING_NOTICES; break; case NotesError.NOTES_ERR_NEWERVERSIONEXISTS: storeError = StoreException.ERR_NEWER_VERSION_EXISTS; break; case NotesError.NOTES_ERR_UNSUPPORTEDACTION: storeError = StoreException.ERR_ACTION_NOT_SUPPORTED; break; case NotesError.NOTES_ERR_NOTACCEPTED: storeError = StoreException.ERR_INVITE_NOT_ACCEPTED; break; case NotesError.NOTES_ERR_OVERWRITEDISALLOWED: storeError = StoreException.ERR_PERSONAL_CHANGES; break; case NotesError.NOTES_ERR_RECURID_NOTFOUND: case NotesError.NOTES_ERR_IDNOTFOUND: storeError = StoreException.ERR_IDENTIFIER_NOT_FOUND; break; case NotesError.NOTES_ERR_ENTRYEXISTS: storeError = StoreException.ERR_ENTRY_EXISTS; break; case NotesError.NOTES_ERR_INVALID_ICALSTR: storeError = StoreException.ERR_INVALID_ICALSTR; break; } return storeError; } }