/* ******************************************************************** Licensed to Jasig under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. Jasig licenses this file to you 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 org.bedework.caldav.bwserver; import org.bedework.caldav.bwserver.PropertyUpdater.Component; import org.bedework.caldav.bwserver.stdupdaters.DateDatetimePropUpdater.DatesState; import org.bedework.caldav.server.sysinterface.SysIntf.UpdateResult; import org.bedework.calfacade.BwAlarm; import org.bedework.calfacade.BwDateTime; import org.bedework.calfacade.BwEvent; import org.bedework.calfacade.base.StartEndComponent; import org.bedework.calfacade.exc.CalFacadeException; import org.bedework.calfacade.svc.EventInfo; import org.bedework.calfacade.util.CalFacadeUtil; import org.bedework.calfacade.util.ChangeTable; import org.bedework.calfacade.util.ChangeTableEntry; import org.bedework.icalendar.IcalCallback; import org.bedework.icalendar.IcalTranslator; import org.bedework.icalendar.Xalarms; import org.bedework.util.calendar.IcalDefs; import org.bedework.util.calendar.PropertyIndex.PropertyInfoIndex; import org.bedework.util.calendar.ScheduleMethods; import org.bedework.util.calendar.XcalUtil; import org.bedework.util.calendar.XcalUtil.TzGetter; import org.bedework.util.timezones.Timezones; import org.bedework.util.xml.tagdefs.XcalTags; import org.bedework.webdav.servlet.shared.WebdavException; import ietf.params.xml.ns.icalendar_2.AvailableType; import ietf.params.xml.ns.icalendar_2.BaseComponentType; import ietf.params.xml.ns.icalendar_2.BaseParameterType; import ietf.params.xml.ns.icalendar_2.BasePropertyType; import ietf.params.xml.ns.icalendar_2.RecurrenceIdPropType; import ietf.params.xml.ns.icalendar_2.UidPropType; import ietf.params.xml.ns.icalendar_2.ValarmType; import ietf.params.xml.ns.icalendar_2.VavailabilityType; import ietf.params.xml.ns.icalendar_2.VeventType; import ietf.params.xml.ns.icalendar_2.VjournalType; import ietf.params.xml.ns.icalendar_2.VtodoType; import ietf.params.xml.ns.icalendar_2.XBedeworkWrapperPropType; import net.fortuna.ical4j.model.Date; import net.fortuna.ical4j.model.Dur; import net.fortuna.ical4j.model.TimeZone; import net.fortuna.ical4j.model.parameter.Value; import net.fortuna.ical4j.model.property.DtEnd; import net.fortuna.ical4j.model.property.DtStart; import org.apache.log4j.Logger; import org.oasis_open.docs.ws_calendar.ns.soap.ComponentReferenceType; import org.oasis_open.docs.ws_calendar.ns.soap.ComponentSelectionType; import org.oasis_open.docs.ws_calendar.ns.soap.ComponentsSelectionType; import org.oasis_open.docs.ws_calendar.ns.soap.ParameterReferenceType; import org.oasis_open.docs.ws_calendar.ns.soap.ParameterSelectionType; import org.oasis_open.docs.ws_calendar.ns.soap.ParametersSelectionType; import org.oasis_open.docs.ws_calendar.ns.soap.PropertiesSelectionType; import org.oasis_open.docs.ws_calendar.ns.soap.PropertyReferenceType; import org.oasis_open.docs.ws_calendar.ns.soap.PropertySelectionType; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import javax.xml.bind.JAXBElement; import javax.xml.namespace.QName; /** Bedework implementation of SysIntf. * * @author Mike Douglass douglm at rpi.edu */ public class BwUpdates { private boolean debug; protected transient Logger log; protected IcalCallback cb; protected String userHref; private Properties state = new Properties(); /** * @param userHref */ public BwUpdates(final String userHref) { debug = getLogger().isDebugEnabled(); this.userHref = userHref; } private static Map<Class, Integer> entTypes = new HashMap<Class, Integer>(); static { entTypes.put(ValarmType.class, IcalDefs.entityTypeAlarm); entTypes.put(VavailabilityType.class, IcalDefs.entityTypeVavailability); entTypes.put(AvailableType.class, IcalDefs.entityTypeAvailable); entTypes.put(VeventType.class, IcalDefs.entityTypeEvent); entTypes.put(VtodoType.class, IcalDefs.entityTypeTodo); entTypes.put(VjournalType.class, IcalDefs.entityTypeJournal); } private PropertyUpdaterRegistry propUpdaterRegistry = new PropertyUpdaterRegistry(); /** Register an updater used by all instances of the updater. i.e. a * standard updater * * @param cl * @param updCl */ public static void registerUpdater(final Class cl, final String updCl) { PropertyUpdaterRegistry.registerStandardUpdater(cl, updCl); } /** Register an updater used only by this instance of the updater. * * @param cl * @param updCl */ public void registerInstanceUpdater(final Class cl, final String updCl) { propUpdaterRegistry.registerUpdater(cl, updCl); } /** Update an event based on a list of web service updates * @param ei * @param updates * @param cb * @return true if updated OK * @throws WebdavException */ public UpdateResult updateEvent(final EventInfo ei, final List<ComponentSelectionType> updates, final IcalCallback cb) throws WebdavException { this.cb = cb; try { if (updates == null) { return new UpdateResult("No updates"); } for (ComponentSelectionType sel: updates) { UpdateResult ur = applyUpdate(ei, sel); if (!ur.getOk()) { return ur; } } return UpdateResult.getOkResult(); } catch (WebdavException we) { if (debug) { error(we); } throw we; } catch (Throwable t) { if (debug) { error(t); } throw new WebdavException(t); } } private UpdateResult applyUpdate(final EventInfo ei, final ComponentSelectionType selPar) throws WebdavException { /* First two selects just get us in to the events */ ComponentSelectionType sel = selPar; // Top level must be "vcalendar" if ((sel == null) || (sel.getVcalendar() == null)) { return new UpdateResult("Not \"vcalendar\""); } // Next - expect "components" ComponentsSelectionType csel = sel.getComponents(); if (csel == null) { return new UpdateResult("Not \"component\""); } /* Only overrides may be added or removed as we only allow updates to * single entities. An override is considered part of a single event. */ for (ComponentReferenceType c: csel.getRemove()) { UpdateResult ur = removeOverride(ei, c); if (!ur.getOk()) { return ur; } } for (ComponentReferenceType c: csel.getAdd()) { UpdateResult ur = addOverride(ei, c); if (!ur.getOk()) { return ur; } } /* Updates may be applied to the master or any overrides selected by uid and * recurrence-id */ for (ComponentSelectionType cs: csel.getComponent()) { BaseComponentType ent = cs.getBaseComponent().getValue(); // Must be a component matching the current one. if (ent == null) { return new UpdateResult("Missing component to match"); } Integer entType = entTypes.get(ent.getClass()); if (entType == null) { return new UpdateResult("Unknown entity type: " + ent.getClass()); } if (entType != ei.getEvent().getEntityType()) { return new UpdateResult("No matching entity"); } List<EventInfo> entities = new ArrayList<EventInfo>(); entities.add(ei); entities.addAll(ei.getOverrides()); entities = match(entities, //sel, ent); if ((entities == null) || (entities.size() == 0)) { return new UpdateResult("No matching entity"); } if ((cs.getProperties() == null) && (cs.getComponents() == null)) { // No properties or components - error return new UpdateResult("Must select \"components\" and/or \"properties\""); } /* At this point we either select properties to change or nested components */ if (cs.getComponents() != null) { UpdateResult ur = applySubCompUpdates(ei, cs.getComponents()); if (!ur.getOk()) { return ur; } } if (cs.getProperties() != null) { // Updating properties UpdateResult ur = updateEventsProperties(entities, cs.getProperties()); if (!ur.getOk()) { return ur; } } } return UpdateResult.getOkResult(); } /** Apply updates to sub-components of the given entity. * * @param ei * @param csel * @return UpdateResult * @throws WebdavException */ private UpdateResult applySubCompUpdates(final EventInfo ei, final ComponentsSelectionType csel) throws WebdavException { /* Deal with removals first */ for (ComponentReferenceType c: csel.getRemove()) { UpdateResult ur = removeSubComp(ei, c); if (!ur.getOk()) { return ur; } } for (ComponentReferenceType c: csel.getAdd()) { UpdateResult ur = addSubComp(ei, c); if (!ur.getOk()) { return ur; } } /* Now any updates to sub-components */ for (ComponentSelectionType cs: csel.getComponent()) { UpdateResult ur = updateSubComp(ei, cs); if (!ur.getOk()) { return ur; } } return UpdateResult.getOkResult(); } /* Return matching entities. */ private List<EventInfo> match(final List<EventInfo> eis, // final SelectElementType sel, final BaseComponentType selComp) throws WebdavException { List<EventInfo> matched = new ArrayList<EventInfo>(); CompSelector cs = getCompSelector(selComp); for (EventInfo ei: eis) { if ((cs.uid == null) && (cs.recurrenceId == null)) { /* Only valid for a single non-recurring entity */ if (ei.getEvent().isRecurringEntity() || (eis.size() > 1)) { return null; } matched.add(ei); return matched; } // matched.addAll(ei.getOverrideProxies()); if (cs.uid == null) { // Not valid return null; } BwEvent ev = ei.getEvent(); // UID must match if (!cs.uid.equals(ev.getUid())) { continue; } if (cs.recurrenceId == null) { // Master only unless all=true // if ((ev.getRecurrenceId() == null) || sel.isAll()) { if (ev.getRecurrenceId() == null) { matched.add(ei); } continue; } // Looking for override if ((ev.getRecurrenceId() != null) && ev.getRecurrenceId().equals(cs.recurrenceId.getDate())) { matched.add(ei); continue; } } return matched; } private static class CompSelector { String uid; BwDateTime recurrenceId; } private CompSelector getCompSelector(final BaseComponentType selComp) throws WebdavException { CompSelector cs = new CompSelector(); for (JAXBElement<? extends BasePropertyType> prop: selComp.getProperties().getBasePropertyOrTzid()) { if (prop.getName().equals(XcalTags.uid)) { cs.uid = ((UidPropType)prop.getValue()).getText(); if ((cs.uid != null) && (cs.recurrenceId != null)) { return cs; } continue; } if (prop.getName().equals(XcalTags.recurrenceId)) { RecurrenceIdPropType rid = (RecurrenceIdPropType)prop.getValue(); XcalUtil.DtTzid dtTzid = XcalUtil.getDtTzid(rid); try { cs.recurrenceId = BwDateTime.makeBwDateTime(dtTzid.dateOnly, dtTzid.dt, dtTzid.tzid); } catch (CalFacadeException cfe) { throw new WebdavException(cfe); } if ((cs.uid != null) && (cs.recurrenceId != null)) { return cs; } continue; } } // for return cs; } private UpdateResult addOverride(final EventInfo ei, final ComponentReferenceType sel) throws WebdavException { try { new IcalTranslator(cb).addOverride(ei, sel.getBaseComponent()); return UpdateResult.getOkResult(); } catch (Throwable t) { throw new WebdavException(t); } } private UpdateResult removeOverride(final EventInfo ei, final ComponentReferenceType sel) throws WebdavException { return new UpdateResult("unimplemented - remove override"); } private UpdateResult addSubComp(final EventInfo ei, final ComponentReferenceType sel) throws WebdavException { try { BwEvent ev = ei.getEvent(); int etype = ev.getEntityType(); BaseComponentType bc = sel.getBaseComponent().getValue(); if (bc instanceof ValarmType) { if ((etype != IcalDefs.entityTypeEvent) || (etype != IcalDefs.entityTypeTodo)) { return new UpdateResult("Invalid entity type for alarm add"); } BwAlarm al = Xalarms.toBwAlarm((ValarmType)bc, false); if (al == null) { return new UpdateResult("Invalid alarm for add"); } ev.addAlarm(al); return UpdateResult.getOkResult(); } return new UpdateResult("Invalid entity type for add"); } catch (CalFacadeException cfe) { throw new WebdavException(cfe); } } private UpdateResult removeSubComp(final EventInfo ei, final ComponentReferenceType sel) throws WebdavException { Component subc = findSubComponent(ei, sel.getBaseComponent().getValue()); if (subc == null) { return new UpdateResult("Invalid sub-component selection for remove"); } ei.getAlarms().remove(subc.getAlarm()); return UpdateResult.getOkResult(); } private UpdateResult updateSubComp(final EventInfo ei, final ComponentSelectionType sel) throws WebdavException { Component subc = findSubComponent(ei, sel.getBaseComponent().getValue()); if (subc == null) { return new UpdateResult("Invalid sub-component selection for update"); } return updateEventProperties(ei, subc, sel.getProperties()); } private Component findSubComponent(final EventInfo ei, final BaseComponentType bc) throws WebdavException { try { BwEvent ev = ei.getEvent(); int etype = ev.getEntityType(); if (bc instanceof ValarmType) { if ((etype != IcalDefs.entityTypeEvent) || (etype != IcalDefs.entityTypeTodo)) { return null; } /* Look for the alarm - we match on the whole component */ BwAlarm matched = null; BwAlarm pattern = Xalarms.toBwAlarm((ValarmType)bc, false); if ((pattern == null) || (ev.getNumAlarms() == 0)) { return null; } for (BwAlarm al: ev.getAlarms()) { if (al.matches(pattern)) { if (matched != null) { return null; // Multiple matches - bad } matched = al; } } return new PropertyUpdateComponent(ei, matched); } return null; } catch (CalFacadeException cfe) { throw new WebdavException(cfe); } } /** * @param eis * @param sel - matching the "properties" element * @return UpdateResult * @throws WebdavException */ private UpdateResult updateEventsProperties(final List<EventInfo> eis, final PropertiesSelectionType sel) throws WebdavException { for (final EventInfo ei: eis) { final UpdateResult ur = updateEventProperties(ei, null, sel); if (!ur.getOk()) { return ur; } } return UpdateResult.getOkResult(); } /** The current selector is for the event properties. * * <p>We might have one or more updates - add or remove * * <p>We might also have one or more selects which allow for change of values * * @param ei * @param subComponent - non null if this is a sub-component update * @param sel - matching the "properties" element * @return true for updated OK * @throws WebdavException */ private UpdateResult updateEventProperties(final EventInfo ei, final Component subComponent, final PropertiesSelectionType sel) throws WebdavException { /* First deal with all the changes */ for (final PropertySelectionType psel: sel.getProperty()) { /* psel represents a selection on a property which must exist and for * which we must have an updater. * * We may be applying changes to the property with a change update and/or * updating the parameters through one or more a selections on the * parameters. */ final BasePropertyType bprop; final QName pname; if (psel.getBaseProperty() == null) { return new UpdateResult("No selection property supplied"); } bprop = psel.getBaseProperty().getValue(); pname = psel.getBaseProperty().getName(); final PropertyUpdater pu = getUpdater(bprop); if (pu == null) { return new UpdateResult("No updater for property: " + pname); } PropertyUpdateInfo ui = new PropertyUpdateInfo(bprop, pname, cb, ei, subComponent, state, userHref); UpdateResult ur = null; /* There is possibly no change - for example we are changing the tzid for * the dtstart */ if (psel.getChange() != null) { ur = ui.setChange(psel.getChange()); if (ur != null) { return ur; } } /* Look for updates to property parameters and add them to the property * updater info. */ if (psel.getParameters() != null) { ui.setParameterUpdates(psel.getParameters()); } // There may be no change ur = pu.applyUpdate(ui); if (!ur.getOk()) { return ur; } } /* Next adds */ UpdateResult ur = addRemove(ei, subComponent, true, sel.getAdd(), cb); if (!ur.getOk()) { return ur; } /* Next removes */ ur = addRemove(ei, subComponent, false, sel.getRemove(), cb); if (!ur.getOk()) { return ur; } /* We now need to validate the result to ensure the changes leave a * consistent entity. Date/time changes in particular can have a number * of side effects. */ ur = validateDates(ei); if (!ur.getOk()) { return ur; } if (debug) { ei.getChangeset(userHref).dumpEntries(); } return UpdateResult.getOkResult(); } private UpdateResult validateDates(final EventInfo ei) throws WebdavException { DatesState ds = (DatesState)state.get(DatesState.stateName); if (ds == null) { return UpdateResult.getOkResult(); } BwEvent ev = ei.getEvent(); boolean task = ev.getEntityType() == IcalDefs.entityTypeTodo; PropertyInfoIndex endPi; ChangeTable chg = ei.getChangeset(userHref); if (task) { endPi = PropertyInfoIndex.DUE; } else { endPi = PropertyInfoIndex.DTEND; } /* We maintain both end and duration - if either changed we need to adjust * the other. */ try { boolean scheduleReply = ev.getScheduleMethod() == ScheduleMethods.methodTypeReply; // No dates valid for reply if (ds.start == null) { if (!scheduleReply && !task) { return new UpdateResult("org.bedework.error.nostartdate"); } /* A todo can have no date and time. set start to now, end to * many years from now and the noStart flag. * * Such an entry has to appear only on the current day. */ if (ds.end != null) { ds.start = ds.end; } else { Date now = new Date(new java.util.Date().getTime()); DtStart dtStart = new DtStart(now); dtStart.getParameters().add(Value.DATE); ds.start = BwDateTime.makeBwDateTime(dtStart); } if (!ev.getNoStart() || !CalFacadeUtil.eqObjval(ev.getDtstart(), ds.start)) { chg.changed(PropertyInfoIndex.DTSTART, ev.getDtstart(), null); ev.setDtstart(ds.start); ev.setNoStart(true); } } else if (ev.getNoStart() || !ds.start.equals(ev.getDtstart())) { chg.changed(PropertyInfoIndex.DTSTART, ev.getDtstart(), ds.start); ev.setNoStart(false); ev.setDtstart(ds.start); } char endType = StartEndComponent.endTypeNone; if (ds.end != null) { if ((ev.getEndType() != StartEndComponent.endTypeDate) || !CalFacadeUtil.eqObjval(ev.getDtend(), ds.end)) { chg.changed(endPi, ev.getDtend(), ds.end); endType = StartEndComponent.endTypeDate; ev.setDtend(ds.end); } } else if (scheduleReply || task) { Dur years = new Dur(520); // about 10 years Date now = new Date(new java.util.Date().getTime()); DtEnd dtEnd = new DtEnd(new Date(years.getTime(now))); dtEnd.getParameters().add(Value.DATE); ds.end = BwDateTime.makeBwDateTime(dtEnd); if (!CalFacadeUtil.eqObjval(ev.getDtend(), ds.end)) { chg.changed(endPi, ev.getDtend(), ds.end); ev.setDtend(ds.end); } } /** If we were given a duration store it in the event and calculate an end to the event - which we should not have been given. */ if (ds.duration != null) { if (endType != StartEndComponent.endTypeNone) { if (ev.getEntityType() == IcalDefs.entityTypeFreeAndBusy) { // Apple is sending both - duration indicates the minimum // freebusy duration. Ignore for now. } else { return new UpdateResult(CalFacadeException.endAndDuration); } } endType = StartEndComponent.endTypeDuration; if (!ds.duration.equals(ev.getDuration())) { chg.changed(PropertyInfoIndex.DURATION, ev.getDuration(), ds.duration); ev.setDuration(ds.duration); } ev.setDtend(BwDateTime.makeDateTime(ev.getDtstart().makeDtStart(), ev.getDtstart().getDateType(), new Dur(ds.duration))); } else if (!scheduleReply && (endType == StartEndComponent.endTypeNone) && !task) { /* No duration and no end specified. * Set the end values to the start values + 1 for dates */ boolean dateOnly = ev.getDtstart().getDateType(); Dur dur; if (dateOnly) { dur = new Dur(1, 0, 0, 0); // 1 day } else { dur = new Dur(0, 0, 0, 0); // No duration } BwDateTime bwDtEnd = BwDateTime.makeDateTime(ev.getDtstart().makeDtStart(), dateOnly, dur); if (!CalFacadeUtil.eqObjval(ev.getDtend(), bwDtEnd)) { chg.changed(endPi, ev.getDtend(), bwDtEnd); ev.setDtend(bwDtEnd); } } if ((endType != StartEndComponent.endTypeDuration) && (ev.getDtstart() != null) && (ev.getDtend() != null)) { // Calculate a duration String durVal = BwDateTime.makeDuration(ev.getDtstart(), ev.getDtend()).toString(); if (!durVal.equals(ev.getDuration())) { chg.changed(PropertyInfoIndex.DURATION, ev.getDuration(), durVal); ev.setDuration(durVal); } } ev.setEndType(endType); return UpdateResult.getOkResult(); } catch (CalFacadeException cfe) { throw new WebdavException(cfe); } } private UpdateResult addRemove(final EventInfo ei, final Component subComponent, final boolean add, final List<PropertyReferenceType> refs, final IcalCallback cb) throws WebdavException { for (PropertyReferenceType r: refs) { BasePropertyType bprop; QName pname; if (r.getBaseProperty() == null) { return new UpdateResult("No new value for add"); } bprop = r.getBaseProperty().getValue(); pname = r.getBaseProperty().getName(); PropertyUpdater pu = getUpdater(bprop); if (pu == null) { return new UpdateResult("No updater for property: " + pname); } PropertyUpdateInfo ui = new PropertyUpdateInfo(bprop, pname, cb, ei, subComponent, state, userHref); UpdateResult ur; if (add) { ur = ui.setAdd(r); } else { ur = ui.setRemove(r); } if (ur != null) { return ur; } ur = pu.applyUpdate(ui); if (!ur.getOk()) { return ur; } } return UpdateResult.getOkResult(); } private PropertyUpdater getUpdater(final Object o) { return propUpdaterRegistry.getUpdater(o); } static class PropertyUpdateComponent implements PropertyUpdater.Component { private Component parent; private EventInfo ei; private BwAlarm alarm; PropertyUpdateComponent(final EventInfo ei) { this.ei = ei; } PropertyUpdateComponent(final EventInfo ei, final BwAlarm alarm) { this.ei = ei; this.alarm = alarm; parent = new PropertyUpdateComponent(ei); } @Override public Component getParent() { return parent; } @Override public EventInfo getEi() { return ei; } @Override public BwEvent getEvent() { return ei.getEvent(); } @Override public BwAlarm getAlarm() { return alarm; } } static class PropertyUpdateInfo implements PropertyUpdater.UpdateInfo { private boolean add; private boolean change; private boolean remove; private TzGetter tzs = new TzGetter() { @Override public TimeZone getTz(final String id) throws Throwable { return Timezones.getTz(id); } }; private BasePropertyType prop; private BasePropertyType updprop; private QName propName; private PropertyInfoIndex pi; private IcalCallback cb; private EventInfo ei; private Component subComponent; private ChangeTable chg; private Properties state; private String userHref; private List<ParameterUpdater.UpdateInfo> paramUpdates = new ArrayList<>(); PropertyUpdateInfo(final BasePropertyType prop, final QName pname, final IcalCallback cb, final EventInfo ei, final Component subComponent, final Properties state, final String userHref) { this.prop = prop; propName = pname; this.cb = cb; this.ei = ei; this.subComponent = subComponent; this.state = state; this.userHref = userHref; if (prop instanceof XBedeworkWrapperPropType) { pi = PropertyInfoIndex.XPROP; } else { pi = PropertyInfoIndex.fromName(pname.getLocalPart()); } if (pi == null) { throw new RuntimeException("unknown property " + pname); } chg = ei.getChangeset(userHref); } UpdateResult setAdd(final PropertyReferenceType r) { add = true; JAXBElement<? extends BasePropertyType> baseProperty = r.getBaseProperty(); if (baseProperty == null) { return new UpdateResult("No add property supplied for " + propName); } if (!baseProperty.getValue().getClass().isInstance(prop)) { return new UpdateResult("Selection property " + propName + " not same as " + baseProperty.getName()); } updprop = baseProperty.getValue(); return null; } UpdateResult setRemove(final PropertyReferenceType r) { remove = true; return null; } UpdateResult setChange(final PropertyReferenceType ct) { change = true; JAXBElement<? extends BasePropertyType> baseProperty = ct.getBaseProperty(); if (baseProperty == null) { return new UpdateResult("No add property supplied for " + propName); } if (!baseProperty.getValue().getClass().isInstance(prop)) { return new UpdateResult("Selection property " + propName + " not same as " + baseProperty.getName()); } updprop = baseProperty.getValue(); return null; } UpdateResult setParameterUpdates(final ParametersSelectionType params) { /* First selections for changes */ for (ParameterSelectionType psel: params.getParameter()) { QName parname = psel.getBaseParameter().getName(); BaseParameterType bselparam = psel.getBaseParameter().getValue(); ParameterUpdateInfo pui = new ParameterUpdateInfo(this, bselparam, parname); UpdateResult ur = pui.setChange(psel.getChange()); if (ur != null) { return ur; } getParamUpdates().add(pui); } /* Now adds and removes */ UpdateResult ur = addRemove(true, params.getAdd()); if (ur != null) { return ur; } return addRemove(false, params.getRemove()); } private UpdateResult addRemove(final boolean add, final List<ParameterReferenceType> refs) { for (ParameterReferenceType r: refs) { BaseParameterType bparam; QName pname; if (r.getBaseParameter() == null) { return new UpdateResult("No parameter value for add/remove"); } bparam = r.getBaseParameter().getValue(); pname = r.getBaseParameter().getName(); ParameterUpdateInfo pui = new ParameterUpdateInfo(this, bparam, pname); UpdateResult ur; if (add) { ur = pui.setAdd(r); } else { ur = pui.setRemove(r); } if (ur != null) { return ur; } getParamUpdates().add(pui); } return null; } @Override public boolean isAdd() { return add; } @Override public boolean isChange() { return change; } @Override public boolean isRemove() { return remove; } @Override public BasePropertyType getProp() { return prop; } @Override public BasePropertyType getUpdprop() { return updprop; } @Override public QName getPropName() { return propName; } @Override public IcalCallback getIcalCallback() { return cb; } @Override public TzGetter getTzs() { return tzs; } @Override public EventInfo getEi() { return ei; } @Override public BwEvent getEvent() { return ei.getEvent(); } @Override public Component getSubComponent() { return subComponent; } @Override public ChangeTableEntry getCte() { return chg.getEntry(pi); } @Override public ChangeTableEntry getCte(final PropertyInfoIndex pi) { return chg.getEntry(pi); } @Override public void saveState(final String name, final Object val) { state.put(name, val); } @Override public Object getState(final String name) { return state.get(name); } @Override public List<ParameterUpdater.UpdateInfo> getParamUpdates() { return paramUpdates; } @Override public String getUserHref() { return userHref; } } static class ParameterUpdateInfo implements ParameterUpdater.UpdateInfo { private boolean add; private boolean change; private boolean remove; private PropertyUpdateInfo propInfo; private BaseParameterType param; private BaseParameterType updparam; private QName paramName; ParameterUpdateInfo(final PropertyUpdateInfo propInfo, final BaseParameterType param, final QName pname) { this.propInfo = propInfo; paramName = pname; this.param = param; } UpdateResult setAdd(final ParameterReferenceType r) { add = true; JAXBElement<? extends BaseParameterType> baseParam = r.getBaseParameter(); if (baseParam == null) { return new UpdateResult("No update parameter supplied for " + paramName); } if (!baseParam.getName().equals(paramName)) { return new UpdateResult("Selection parameter name " + paramName + " not equal to " + baseParam.getName()); } updparam = baseParam.getValue(); return null; } UpdateResult setRemove(final ParameterReferenceType r) { remove = true; return null; } UpdateResult setChange(final ParameterReferenceType r) { change = true; JAXBElement<? extends BaseParameterType> baseParam = r.getBaseParameter(); if (baseParam == null) { return new UpdateResult("No update parameter supplied for " + paramName); } if (!baseParam.getName().equals(paramName)) { return new UpdateResult("Selection parameter name " + paramName + " not equal to " + baseParam.getName()); } updparam = baseParam.getValue(); return null; } @Override public boolean isAdd() { return add; } @Override public boolean isChange() { return change; } @Override public boolean isRemove() { return remove; } @Override public PropertyUpdater.UpdateInfo getPropInfo() { return propInfo; } @Override public BaseParameterType getParam() { return param; } @Override public BaseParameterType getUpdparam() { return updparam; } @Override public QName getParamName() { return paramName; } } /* ==================================================================== * Protected methods * ==================================================================== */ protected Logger getLogger() { if (log == null) { log = Logger.getLogger(this.getClass()); } return log; } protected void trace(final String msg) { getLogger().debug(msg); } protected void debugMsg(final String msg) { getLogger().debug(msg); } protected void warn(final String msg) { getLogger().warn(msg); } protected void error(final Throwable t) { getLogger().error(this, t); } protected void logIt(final String msg) { getLogger().info(msg); } }