/*
* FeedSyncronizer.java
*
* Created on April 11, 2007, 8:13 AM
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*/
package org.atomojo.app.sync;
import java.io.IOException;
import java.net.URI;
import java.sql.SQLException;
import java.util.Date;
import java.util.Iterator;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.atomojo.app.AtomResource;
import org.atomojo.app.Storage;
import org.atomojo.app.auth.AuthCredentials;
import org.atomojo.app.client.FeedClient;
import org.atomojo.app.client.StatusException;
import org.atomojo.app.db.DB;
import org.atomojo.app.db.Entry;
import org.atomojo.app.db.EntryMedia;
import org.atomojo.app.db.Feed;
import org.atomojo.app.db.Journal;
import org.atomojo.app.db.SyncProcess;
import org.infoset.xml.XMLException;
import org.restlet.Client;
import org.restlet.Context;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.data.ChallengeResponse;
import org.restlet.data.ChallengeScheme;
import org.restlet.data.Method;
import org.restlet.data.Protocol;
import org.restlet.data.Status;
import org.restlet.representation.Representation;
/**
*
* @author alex
*/
public class PushSynchronizer implements Synchronizer
{
DB db;
Storage storage;
Logger log;
String username;
String password;
SyncProcess proc;
Date syncTime;
int errorCount;
/** Creates a new instance of FeedSyncronizer */
public PushSynchronizer(Logger log,DB db,Storage storage,SyncProcess proc)
{
this.db = db;
this.storage = storage;
this.log = log;
this.username = "admin";
this.password = "admin";
this.proc = proc;
this.syncTime = null;
if (proc.isPullSynchronization()) {
throw new IllegalArgumentException("The sync process "+proc.getName()+" is not push synchronization.");
}
this.errorCount = 0;
}
public int getErrorCount() {
return errorCount;
}
public SyncProcess getProcess() {
return proc;
}
public Date getSynchronizedAt() {
return syncTime;
}
public void setSynchronizationAt(Date time)
{
this.syncTime = time;
}
public void sync()
throws SyncException
{
errorCount = 0;
if (syncTime==null) {
syncTime = new Date();
}
if (proc.getRemoteApp()==null) {
throw new SyncException("The remote application cannot be found.");
}
if (proc.getRemoteApp().isAvailable()) {
try {
String startPath = proc.getSyncTarget().getPath();
if (startPath.length()>0) {
errorCount++;
throw new SyncException("Paths are not current supported for push synchronization.");
}
Date lastSync = proc.getLastSynchronizedOn();
log.info("Synchronizing "+proc.getRemoteApp().getRoot()+", "+(lastSync==null ? "first sync." : "last sync was at "+AtomResource.toXSDDate(lastSync)));
Iterator<Journal.Entry> entries = db.getJournal().getDeletesSince(lastSync,syncTime);
AuthCredentials auth = proc.getRemoteApp().getAuthCredentials();
while (entries.hasNext()) {
Journal.Entry jentry = entries.next();
if (jentry.getOperation()==Journal.DELETE_OPERATION) {
Journal.DeleteEntry deleteEntry = (Journal.DeleteEntry)jentry;
UUID feed = deleteEntry.getFeed();
UUID entry = deleteEntry.getEntry();
URI feedURI = proc.getRemoteApp().getRoot().resolve(deleteEntry.getPath());
if (entry==null) {
// delete feed
log.info("Deleting feed "+feedURI);
FeedClient appClient = new FeedClient(feedURI);
if (auth!=null) {
appClient.setIdentity(auth.getName(),auth.getPassword());
}
Status status = appClient.delete();
if (!status.isSuccess() && !status.equals(Status.CLIENT_ERROR_NOT_FOUND)) {
errorCount++;
log.severe("Cannot delete feed "+feedURI+", stopping synchronization.");
throw new SyncException("Synchronization was incomplete. Stopped due to failure to delete feed: "+feedURI);
}
} else {
// delete entry
log.info("Deleting entry "+entry+" in feed "+feedURI);
FeedClient appClient = new FeedClient(feedURI);
if (auth!=null) {
appClient.setIdentity(auth.getName(),auth.getPassword());
}
Status status = appClient.deleteEntry(entry);
if (!status.isSuccess() && !status.equals(Status.CLIENT_ERROR_NOT_FOUND)) {
errorCount++;
log.severe("Cannot delete feed "+feedURI+", stopping synchronization.");
throw new SyncException("Synchronization was incomplete. Stopped due to failure to delete feed: "+feedURI);
}
}
}
}
entries = db.getJournal().getUpdatesSince(lastSync,syncTime);
while (entries.hasNext()) {
Journal.UpdatedEntry updateEntry = (Journal.UpdatedEntry)entries.next();
Feed feed = updateEntry.getFeed();
Entry entry = updateEntry.getEntry();
String path = feed.getPath();
if (path.length()>0 && !path.endsWith("/")) {
path += "/";
}
URI feedURI = proc.getRemoteApp().getRoot().resolve(path);
FeedClient appClient = new FeedClient(feedURI);
if (auth!=null) {
appClient.setIdentity(auth.getName(),auth.getPassword());
}
if (updateEntry.getOperation()==Journal.CREATE_OPERATION) {
// Create feed or entry
if (entry==null) {
// Create feed
log.info("Creating feed "+feedURI);
try {
Representation feedRep = storage.getFeed(path,feed.getUUID(),feed.getEntries());
// Now, create the feed with the XML
try {
String xml = feedRep.getText();
if (!appClient.create(xml).isSuccess()) {
errorCount++;
log.severe("Cannot create feed on target for path "+path);
throw new SyncException("Synchronization was incomplete. Stopped due to failure to create feed: "+path);
}
} catch (IOException ex) {
errorCount++;
log.log(Level.SEVERE,"Cannot get xml serialization of feed document for path "+path,ex);
throw new SyncException("Synchronization was incomplete. Stopped due to failure of serialization at path "+path);
}
} catch (IOException ex) {
errorCount++;
log.log(Level.SEVERE,"Cannot get feed document for path "+path,ex);
throw new SyncException("Synchronization was incomplete. Stopped due failure to get feed at "+path);
}
} else {
// Create entry
log.info("Creating entry "+entry.getUUID()+" for feed "+feedURI);
// Check for media entries
Iterator<EntryMedia> media = entry.getResources();
if (media.hasNext()) {
// We have a media entry and so we post the media first
EntryMedia resource = media.next();
if (media.hasNext()) {
while (media.hasNext()) {
media.next();
}
errorCount++;
log.severe("Media entries with more than one resource is not supported.");
throw new SyncException("Synchronization was incomplete. Media entry for entry "+entry.getUUID()+" has more than one resource.");
}
// Get the media from the XML DB
try {
Representation rep = storage.getMedia(path,feed.getUUID(),resource.getName());
// Send the media to the server
rep.setMediaType(resource.getMediaType());
appClient.createMedia(entry.getUUID(),resource.getName(),rep);
} catch (StatusException ex) {
errorCount++;
log.severe("Cannot update media "+resource.getName()+" on target for path "+path+", status="+ex.getStatus().getCode());
throw new SyncException("Synchronization was incomplete. Failure to update media "+resource.getName()+" for entry "+entry.getUUID()+", status="+ex.getStatus().getCode());
} catch (Exception ex) {
errorCount++;
log.log(Level.SEVERE,"Cannot get media "+resource.getName()+" for path "+path+" and id "+entry.getUUID().toString(),ex);
throw new SyncException("Synchronization was incomplete. Failure to get media "+resource.getName()+" for entry "+entry.getUUID());
}
} else {
String xml = null;
// Get the entry document from the XML DB
try {
Representation entryRep = storage.getEntry(".",path,feed.getUUID(),entry.getUUID());
// Now, create the feed with the XML
try {
xml = entryRep.getText();
} catch (IOException ex) {
errorCount++;
log.log(Level.SEVERE,"Cannot get xml serialization of entry document for path "+path,ex);
throw new SyncException("Synchronization was incomplete. Failure to get serialization of entry "+entry.getUUID());
}
} catch (IOException ex) {
errorCount++;
log.log(Level.SEVERE,"Cannot get entry document for path "+path+" and id "+entry.getUUID().toString(),ex);
throw new SyncException("Synchronization was incomplete. Failure to get entry "+entry.getUUID());
}
// This is a regular entry
try {
appClient.createEntry(xml);
} catch (Exception ex) {
errorCount++;
log.severe("Cannot create entry on target for path "+path);
throw new SyncException("Synchronization was incomplete. Failure to create entry "+entry.getUUID()+" for path "+path);
}
}
}
} else {
EntryMedia resource = updateEntry.getResource();
if (entry==null) {
// Modify feed
log.info("Updating feed "+feedURI);
// Get the feed document from the XML DB
try {
Representation feedRep = storage.getFeed(path,feed.getUUID(),feed.getEntries());
// Now, create the feed with the XML
try {
String xml = feedRep.getText();
if (!appClient.update(xml).isSuccess()) {
errorCount++;
log.severe("Cannot update feed on target for path "+path);
throw new SyncException("Synchronization was incomplete. Cannot update feed for path "+path);
}
} catch (IOException ex) {
errorCount++;
log.log(Level.SEVERE,"Cannot get xml serialization of feed document for path "+path,ex);
throw new SyncException("Synchronization was incomplete. Cannot get serialization of feed for path "+path);
}
} catch (IOException ex) {
errorCount++;
log.log(Level.SEVERE,"Cannot get feed document for path "+path,ex);
throw new SyncException("Synchronization was incomplete. Cannot get feed for path "+path);
}
} else if (resource==null) {
// Modify entry
log.info("Updating entry "+entry.getUUID()+" for feed "+feedURI);
// Get the entry document from the XML DB
try {
Representation entryRep = storage.getEntry(".",path,feed.getUUID(),entry.getUUID());
// Now, create the feed with the XML
try {
String xml = entryRep.getText();
if (!appClient.updateEntry(entry.getUUID(),xml).isSuccess()) {
errorCount++;
log.severe("Cannot update entry on target for path "+path);
throw new SyncException("Synchronization was incomplete. Cannot update entry "+entry.getUUID()+" for path"+path);
}
} catch (IOException ex) {
errorCount++;
log.log(Level.SEVERE,"Cannot get xml serialization of entry document for path "+path,ex);
throw new SyncException("Synchronization was incomplete. Cannot serialization of entry "+entry.getUUID()+" for path"+path);
}
} catch (IOException ex) {
errorCount++;
log.log(Level.SEVERE,"Cannot get entry document for path "+path+" and id "+entry.getUUID().toString(),ex);
throw new SyncException("Synchronization was incomplete. Cannot get entry "+entry.getUUID()+" for path"+path);
}
} else {
// Modify resource
log.info("Updating resource "+resource.getName()+" for entry "+entry.getUUID()+" for feed "+feedURI);
// Get the media from the XML DB
try {
Representation mediaRep = storage.getMedia(path,feed.getUUID(),resource.getName());
mediaRep.setMediaType(resource.getMediaType());
if (!appClient.updateMedia(resource.getName(),mediaRep).isSuccess()) {
errorCount++;
log.severe("Cannot update media "+resource.getName()+" on target for path "+path);
throw new SyncException("Synchronization was incomplete. Cannot update media "+resource.getName()+" for entry "+entry.getUUID()+" for path"+path);
}
} catch (IOException ex) {
errorCount++;
log.log(Level.SEVERE,"Cannot get media "+resource.getName()+" for path "+path+" and id "+entry.getUUID().toString(),ex);
throw new SyncException("Synchronization was incomplete. Cannot get media "+resource.getName()+" for entry "+entry.getUUID()+" for path"+path);
}
}
}
}
proc.setLastSynchronizedOn(syncTime);
proc.marshall();
proc.update();
} catch (SQLException ex) {
errorCount++;
log.log(Level.SEVERE,"Cannot traverse journal due to SQL exception: "+ex.getMessage(),ex);
} catch (XMLException ex) {
errorCount++;
log.log(Level.SEVERE,"Cannot iterate sync targets due to XML exception: "+ex.getMessage(),ex);
}
} else {
errorCount++;
log.warning("Target "+proc.getName()+" -> "+proc.getRemoteApp().getRoot()+" is not available.");
throw new TargetNotAvailableException("Target "+proc.getName()+" -> "+proc.getRemoteApp().getRoot()+" is not available.");
}
}
protected boolean feedExists(URI location)
{
Client client = new Client(new Context(log),Protocol.valueOf(location.getScheme()));
client.getContext ().getAttributes().put("hostnameVerifier", org.apache.commons.ssl.HostnameVerifier.DEFAULT);
Request request = new Request(Method.HEAD,location.toString());
request.setChallengeResponse(new ChallengeResponse(ChallengeScheme.HTTP_BASIC,username,password));
Response response = client.handle(request);
return response.getStatus().isSuccess();
}
}