// Copyright 2007 Google Inc.
//
// 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.google.enterprise.connector.afyd;
import com.google.enterprise.connector.spi.RepositoryException;
import com.google.gdata.data.DateTime;
import com.google.gdata.data.Entry;
import com.google.gdata.data.Feed;
import com.google.gdata.client.Query;
import com.google.gdata.client.calendar.CalendarService;
import com.google.gdata.util.AuthenticationException;
import com.google.gdata.util.ServiceException;
import com.google.gdata.util.NotModifiedException;
import java.net.URL;
import java.net.MalformedURLException;
import java.io.IOException;
import java.util.List;
import java.util.LinkedList;
import java.util.logging.Logger;
import java.util.Comparator;
import java.util.Collections;
/**
* This class fills the EntryFeedProvider role for the case of the hosted
* Calendar service. A GData CalendarService is needed to power this class.
*
* @author amsmith@google.com (Adam Smith)
*/
public class CalendarEntryProvider implements FeedEntryProvider {
/** The logger for this class. */
private static final Logger LOGGER =
Logger.getLogger(CalendarEntryProvider.class.getName());
/**
* The maximum number of entries to be fetched when requesting new items
* from the feed. If more than this number of results are available,
* last-updated-time traversal order is NOT guaranteed and some updates
* can be missed.
*/
private static final int MAX_RESULTS = Integer.MAX_VALUE;
/** The service that will actually be used to fetch feed entries. */
private CalendarService service;
private String domain;
private Comparator comparator;
private String lastEntryId;
private String lastEntryCheckpoint;
public static final String EVENT_FEED_PATTERN =
"https://www.google.com/calendar/feeds/{userName}@{domain}/private/basic";
/**
* Constructs a CalendarEntryProvider using the given service for fetching
* feed entries.
* @param calendarService the back end service in question
* @param email is the domain admin's email
* @param password is the domain admin's password
*/
public CalendarEntryProvider
(CalendarService calendarService, String email, String password)
throws AuthenticationException {
calendarService.setUserCredentials(email, password);
this.service = calendarService;
this.domain = email.substring(email.indexOf("@") + 1);
this.comparator = new TraversalOrderComparator();
}
/**
* {@inheritDoc}
*
* The chosen traversal order for this feed provider is "by id alphabetically,
* by last modified time".
*/
public List getOrderedEntriesForUser(String username, String checkpoint)
throws RepositoryException {
String urlString = EVENT_FEED_PATTERN
.replaceAll("\\{userName\\}", username)
.replaceAll("\\{domain\\}", domain);
URL feedUrl = null;
try {
feedUrl = new URL(urlString);
} catch (MalformedURLException murle) {
LOGGER.severe(murle.toString());
throw new RepositoryException(murle);
}
DateTime ifModifiedSince = null;
if (checkpoint != null) {
String dateString = checkpoint.substring(0, checkpoint.indexOf("!"));
try {
ifModifiedSince = DateTime.parseDateTime(dateString);
} catch (NumberFormatException nfe) {
LOGGER.info("Got " + nfe.toString() + " while parsing date part of"
+ "checkpoint. Continuing as if no date was specified.");
}
}
// If ifModifiedSince is null at this point service.query() will treat it
// as if no threshold was specified (equivalent to the 2-arg form).
DateTime fetchTime = DateTime.now();
Query query = new Query(feedUrl);
query.setMaxResults(MAX_RESULTS);
if (ifModifiedSince != null) {
// The use of ifModifiedSince here filters out entries that were
// modified before the given date. Logically, we only care about those
// entries that were modified recently.
query.setUpdatedMin(ifModifiedSince);
}
Feed feed = null;
try {
feed = (Feed) service.query(query, Feed.class, ifModifiedSince);
} catch (NotModifiedException nme) {
// excellent! no work to do
return new LinkedList();
} catch (ServiceException se) {
LOGGER.severe(se.toString());
throw new RepositoryException(se);
} catch (IOException ioe) {
LOGGER.severe(ioe.toString());
throw new RepositoryException(ioe);
}
List entries = feed.getEntries();
Collections.sort(entries, comparator);
Entry lastEntry = (Entry) entries.get(entries.size() - 1);
lastEntryId = lastEntry.getId();
lastEntryCheckpoint = fetchTime.toString() + "!" + lastEntryId;
if (checkpoint != null && ifModifiedSince != null) {
// All of these entries were modified during or after the checkpoint time
// but we still need to skip over any that have been processed before
// during the checkpoint time.
String idString = checkpoint.substring(checkpoint.indexOf("!") + 1);
while (entries.size() > 0) {
Entry firstEntry = (Entry) entries.get(0);
boolean isIdLessEq =
firstEntry.getId().compareTo(idString) <= 0;
boolean isUpdatedEqual =
firstEntry.getUpdated().compareTo(ifModifiedSince) == 0;
if (isIdLessEq && isUpdatedEqual) {
entries.remove(0);
} else {
break;
}
}
}
return entries;
}
/**
* {@inheritDoc}
*
* The checkpoint method for this feed provider is "LASTMOD_TIME!ID".
*/
public String getCheckpointForEntry(Entry entry) {
if (lastEntryId != null && lastEntryId.equals(entry.getId())) {
return lastEntryCheckpoint;
} else {
return entry.getUpdated().toString() + "!" + entry.getId();
}
}
/**
* This class implements Comparator for the traversal order described above.
*/
public static class TraversalOrderComparator implements Comparator {
public int compare(Object o1, Object o2) {
Entry e1 = (Entry) o1;
Entry e2 = (Entry) o2;
int updatedResult = e1.getUpdated().compareTo(e2.getUpdated());
if (updatedResult == 0) {
return e1.getId().compareTo(e2.getId());
} else {
return updatedResult;
}
}
public boolean equals(Object o1, Object o2) {
Entry e1 = (Entry) o1;
Entry e2 = (Entry) o2;
return e1.getId().compareTo(e1.getId()) == 0;
}
}
}