package org.osaf.caldav4j.util;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.fortuna.ical4j.model.Calendar;
import net.fortuna.ical4j.model.Component;
import net.fortuna.ical4j.model.Date;
import net.fortuna.ical4j.model.DateTime;
import org.apache.commons.lang.StringUtils;
import org.osaf.caldav4j.CalDAVConstants;
import org.osaf.caldav4j.exceptions.CalDAV4JException;
import org.osaf.caldav4j.exceptions.CalDAV4JProtocolException;
import org.osaf.caldav4j.exceptions.DOMValidationException;
import org.osaf.caldav4j.model.request.CalDAVProp;
import org.osaf.caldav4j.model.request.CalendarData;
import org.osaf.caldav4j.model.request.CalendarQuery;
import org.osaf.caldav4j.model.request.Comp;
import org.osaf.caldav4j.model.request.CompFilter;
import org.osaf.caldav4j.model.request.PropFilter;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
/**
*
* Copyright 2008 Roberto Polli
* this class is an helper for creating Calendar-Query REPORTs
*
* because of the complexity of iCalendar object and relative queries,
* this class is intended to help the creation of basic queries like
* get all VEVENT with THOSE attributes in THIS time-range
*
* main schema is a strongly typed class,
* with helpers/parsers that will create
* the caldav query
*
* usage:
*
* QueryGenerator qg = new QueryGenerator();
* qg.setComponent("VEVENT"); // retrieve the whole VEVENT
* qg.setComponent("VEVENT : UID, ATTENDEE, DTSTART, DTEND"); // retrieve the given properties
*
* qg.setFilter("VEVENT"); //request on VEVENT
* qg.setFilter("VEVENT [start;end] : UID==value1 , DTSTART==[start;end], DESCRIPTION==UNDEF, SUMMARY!=not my summary,")
*
* start and end values can be empty strings "" or RFC2445-UTC timestamp
*/
/**
* This Class is an helper for creating CalDAV queries.
* @since 0.5
* @experimental this class is experimental
*/
public class GenerateQuery implements CalDAVConstants {
// constants
private static final String caldavNameSpaceQualifier = NS_QUAL_CALDAV;
// component attributes
String requestedComponent = null; // VEVENT, VTODO
List<String> requestedComponentProperties = new ArrayList<String>(); // a list of properties to be retrieved
// Nested object queries should be managed nesting two generated queries
String filterComponent = null; // VEVENT, VTODO
List<String> filterComponentProperties = new ArrayList<String>();
Date timeRangeStart = null;
Date timeRangeEnd = null;
boolean allProp = true;
boolean noCalendarData = false;
public void setNoCalendarData(boolean p) {
this.noCalendarData = p;
}
// other settings: collation
String collation = null; // use TextMatch default value
// comp flags
//TODO limit-recurrence-set, limit-freebusy-set, get-etag
private Date recurrenceSetEnd;
private Date recurrenceSetStart;
private Integer expandOrLimit;
/**
* Create a GenerateQuery object with the given parameters
* NB: DON'T use spaces in comp and filter unless you REALLY need spaces
* @param comp COMPONENT : PROP1,PROP2,..,PROPn
* @param filter COMPONENT : PROP1==VALUE1,PROP2!=VALUE2
* @throws CalDAV4JException
*/
public GenerateQuery(String component, String filterComponent)
throws CalDAV4JException {
setComponent(component);
setFilter(filterComponent);
}
/**
*
*/
public GenerateQuery() {
}
/**
* constructor with Comp & CompFilter
*
* XXX too low-level
*
*/
protected GenerateQuery(String c, List<String> cProp,
String cFilter, List<String> pFilter) {
this(c, cProp, cFilter, pFilter, null);
}
/**
* constructor with Comp & CompFilter & collation
*/
protected GenerateQuery(String c, List<String> cProp,
String cFilter, List<String> pFilter, String collation) {
setComponent(c, cProp);
setFilter(cFilter, pFilter);
this.collation = collation;
}
/**
* constructor with Comp & CompFilter as arrays
* @deprecated
*/
private GenerateQuery(String c, String cProp[],
String cFilter, String pFilter[]) {
this(c, cProp != null ? Arrays.asList(cProp) : null,
cFilter, pFilter != null ? Arrays.asList(pFilter) : null);
}
/**
* validator
* TODO this method should provide at least a basic validation of the calendar-query
* @deprecated This method does nothing but returning true!!!
*/
public boolean validate() {
return true;
}
/**
* set the component to retrieve: VEVENT, VTODO
* VEVENT : UID, DTSTART, DTEND,
*
*/
public void setComponent(String component) {
if (component != null) {
String cl[] = null;
String c[] = component.trim().split("\\s*:\\s*",2);
setRequestedComponent(c[0]);
// if a list of properties is specified, then remove the allprop tag
if (c.length>1){
allProp = false;
cl = c[1].trim().split("\\s*,\\s*");
this.requestedComponentProperties = Arrays.asList(cl);
}
}
}
/**
* set the component and properties to retrieve: VEVENT, VTODO
*/
public void setComponent(String component, List<String> props) {
if (component != null) {
setRequestedComponent(component);
this.requestedComponentProperties = props;
}
}
/**
* transform the requestedComponentProperties fields in a PropComp value
*/
private Comp getComp() {
Comp vCalendarComp = new Comp();
vCalendarComp.setName(Calendar.VCALENDAR);
if (requestedComponent != null) {
Comp vEventComp = new Comp();
vEventComp.setName(requestedComponent);
for (String propertyName : requestedComponentProperties ) {
// add properties to VCALENDAR.VEVENT
vEventComp.addProp(new CalDAVProp(NS_QUAL_CALDAV, "name", propertyName, false, false)); // @see modification to CalDAVProp
}
// add only one component...maybe more ;)
List <Comp> comps = new ArrayList<Comp> ();
try {
vEventComp.validate();
comps.add(vEventComp);
} catch (DOMValidationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
vCalendarComp.setComps(comps);
}
return vCalendarComp;
}
/**
* set the component to filter: VEVENT, VTODO
* syntax:
* VTODO [;] : UID==1231423423145231 , DTSTART=[1214313254324;$3214234231] , SUMMARY!=Caldav4j
* @throws ParseException
*/
public void setFilter(String filterComponent)
throws CalDAV4JException {
if (filterComponent != null) {
String c[] = filterComponent.split("\\s*:\\s*",2); // split string in two
Pattern compFilterString = Pattern.compile("(.+?)\\s*(\\[(.*?);(.*?)\\])?");
Matcher m = compFilterString.matcher(c[0]);
if (m.matches()) {
setFilterComponent(m.group(1));
// a time-range filter
if (m.group(4) != null) {
timeRangeStart = parseTime(m.group(3));
timeRangeEnd = parseTime(m.group(4));
}
if (c.length>1){
String cl[] = c[1].trim().split("\\s*,\\s*");
this.filterComponentProperties = Arrays.asList(cl);
}
}
}
}
/**
* set the component and properties to filter: VEVENT, VTODO
*/
public void setFilter(String filterComponent, List<String> props) {
if (filterComponent != null) {
setFilterComponent(filterComponent);
setFilterComponentProperties(props);
}
}
/**
* transform filterComponentProperties in a List of PropFilter
* this method parses
* @throws ParseException
*/
private List<PropFilter> getPropFilters()
throws CalDAV4JException {
List<PropFilter> pf = new ArrayList<PropFilter>();
Pattern filter = Pattern.compile("(.+?)([!=]=)(\\[(.*?);(.*?)\\]|([^\\]].+))");
for (String p : this.filterComponentProperties) {
String name = null;
Boolean isDefined = null;
boolean negateCondition = false;
Date timeRangeStart = null, timeRangeEnd = null;
Boolean isTextmatchcaseless = true;
String textmatchString = null;
//
// parse: UID==3oij312po3214432 , DESCRIPTION!=Spada , DTSTART==[b;e]
//
Matcher str = filter.matcher(p);
if (str.matches() && (str.group(3) != null ) ) {
name = str.group(1);
negateCondition = "!=".equals(str.group(2));
if (str.group(4) == null ) {
// standard filter
if ("UNDEF".equals(str.group(3))) {
isDefined = false;
} else {
textmatchString = str.group(3);
}
} else if (str.group(5) != null ) {
// a time-range filter
timeRangeStart = parseTime(str.group(4));
timeRangeEnd = parseTime(str.group(5));
}
List<String> componentList = Arrays.asList(new String[] {
Component.VALARM, Component.VEVENT, Component.VFREEBUSY, Component.VJOURNAL, Component.VTIMEZONE, Component.VTODO, Component.VVENUE });
if (! componentList.contains(name)) {
pf.add(new PropFilter(NS_QUAL_CALDAV, name, isDefined,
timeRangeStart, timeRangeEnd,
isTextmatchcaseless, negateCondition, this.collation, textmatchString, null));
} else {
// if there, filter is invalid: we needed a comp-filter, not prop-filter
}
} else {
// not a valid filter
}
}
return pf;
}
/**
* transform filters in a CompFilter
* @throws ParseException
*/
private CompFilter getFilter()
throws CalDAV4JException {
// search for VCALENDAR matching...
CompFilter vCalendarCompFilter = new CompFilter(NS_QUAL_CALDAV);
vCalendarCompFilter.setName(Calendar.VCALENDAR);
// parse filterComponent
if (this.filterComponent != null ) {
CompFilter vEventCompFilter = new CompFilter(NS_QUAL_CALDAV, this.filterComponent,
false, timeRangeStart, timeRangeEnd, /// isDefined, dateStart, dateEnd
null,getPropFilters().size()==0 ? null : getPropFilters());
try {
vEventCompFilter.validate();
vCalendarCompFilter.addCompFilter(vEventCompFilter);
} catch (DOMValidationException e) {
// if filter is bad, don't add nothing
e.printStackTrace();
}
}
return vCalendarCompFilter;
}
/**
* Create a CalendarQuery
* @deprecated Use generate() instead;
*/
public CalendarQuery generateQuery() throws CalDAV4JException {
return generate();
}
/**
* this should parse QueryGenerator attributes
* and create the CalendarQuery
* @param recurrenceSetStart
* @throws CalDAV4JException
* @throws ParseException
*/
public CalendarQuery generate()
throws CalDAV4JException {
CalendarQuery query = new CalendarQuery(NS_QUAL_CALDAV, NS_QUAL_DAV);
query.addProperty(CalDAVConstants.PROP_GETETAG);
if (allProp) {
query.addProperty(CalDAVConstants.PROP_ALLPROP);
}
if (!noCalendarData) {
// TODO limit-recurrence-set
CalendarData calendarData = new CalendarData(NS_QUAL_CALDAV);
if (recurrenceSetEnd!=null || recurrenceSetStart!=null ) {
calendarData.setExpandOrLimitRecurrenceSet(expandOrLimit);
calendarData.setRecurrenceSetStart(recurrenceSetStart);
calendarData.setRecurrenceSetEnd(recurrenceSetEnd);
}
calendarData.setComp(getComp());
query.setCalendarDataProp(calendarData);
} else {
if (this.recurrenceSetEnd != null || this.recurrenceSetStart != null) {
throw new CalDAV4JProtocolException("Bad query: you set noCalendarData but you have limit-recurrence-set");
}
}
query.setCompFilter(getFilter());
query.validate();
return query;
}
public String prettyPrint() {
//query.validate();
try {
Document doc = generate().createNewDocument(XMLUtils
.getDOMImplementation());
return XMLUtils.toPrettyXML(doc);
} catch (DOMValidationException domve) {
throw new RuntimeException(domve);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
return null;
}
// getters+setters:
// remove trailing spaces from queries and get better input
public void setRequestedComponent(String c) {
if (c != null) {
this.requestedComponent = c.trim();
}
}
public void setFilterComponent(String c) {
if (c != null) {
this.filterComponent = c.trim();
}
}
// if passed variable is null, a new ArrayList<String> remains
public void setFilterComponentProperties(List<String> a) {
if (a != null) {
this.filterComponentProperties = a;
}
}
public void setRequestedComponentProperties(
List<String> requestedComponentProperties) {
if(requestedComponentProperties != null) {
this.requestedComponentProperties = requestedComponentProperties;
}
}
public void setTimeRange(Date start, Date end) {
this.timeRangeStart = start;
this.timeRangeEnd = end;
}
// TODO testme
public void setRecurrenceSet(String start, String end, Integer expandOrLimit) {
if (StringUtils.isNotBlank(start)) {
try {
this.recurrenceSetStart = parseTime(start);
} catch (CalDAV4JException e) {
// TODO write a log class
e.printStackTrace();
}
}
if (StringUtils.isNotBlank(end)) {
try {
this.recurrenceSetEnd = parseTime(end);
} catch (CalDAV4JException e) {
// TODO write a log class
e.printStackTrace();
}
}
switch (expandOrLimit) {
case 1:
case 0:
this.expandOrLimit = expandOrLimit;
break;
default:
//TODO error validating
break;
}
}
/**
* return the xml query
* @param query
* @return
* @throws DOMValidationException
*/
public static String printQuery(CalendarQuery query)
throws DOMValidationException
{
try {
query.validate();
Document doc = query.createNewDocument(XMLUtils
.getDOMImplementation());
return XMLUtils.toPrettyXML(doc);
} catch (DOMException e) {
throw new DOMValidationException(e.getMessage(), e);
}
}
/**
* parses a string to a Date using the following syntax:
* - null or "" to null
* - parsable to Date(parsable)
* - NOW to Date(true) UTC
* @param time
* @return Date(time)
* @throws ParseException
*/
private Date parseTime(String time) throws CalDAV4JException {
if (time != null && !"".equals(time)) {
if ("NOW".equals(time)) {
return new DateTime(true);
} else {
try {
if (time.length()>8) {
return new DateTime(time);
} else {
return new Date(time);
}
} catch (ParseException e) {
throw new CalDAV4JException("Unparsable date format in query:"+time, e);
}
}
}
return null;
}
}
//