Java tutorial
/* * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.xwoot.xwootApp; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.ObjectOutputStream; import java.io.StringReader; import java.net.URI; import java.net.URISyntaxException; import java.security.InvalidParameterException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Set; import java.util.zip.ZipFile; import jlibdiff.Diff; import jlibdiff.Hunk; import jlibdiff.HunkAdd; import jlibdiff.HunkChange; import jlibdiff.HunkDel; import net.jxta.document.AdvertisementFactory; import net.jxta.jxtacast.event.JxtaCastEvent; import net.jxta.jxtacast.event.JxtaCastEventListener; import net.jxta.peergroup.PeerGroup; import net.jxta.pipe.PipeID; import net.jxta.pipe.PipeService; import net.jxta.platform.NetworkConfigurator; import net.jxta.protocol.PeerGroupAdvertisement; import net.jxta.protocol.PipeAdvertisement; import org.apache.commons.io.FileUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.xwoot.contentprovider.XWootContentProviderException; import org.xwoot.contentprovider.XWootContentProviderInterface; import org.xwoot.contentprovider.XWootId; import org.xwoot.contentprovider.XWootObject; import org.xwoot.contentprovider.XWootObjectField; import org.xwoot.antiEntropy.AntiEntropy; import org.xwoot.antiEntropy.AntiEntropyException; import org.xwoot.clockEngine.ClockException; import org.xwoot.jxta.DirectMessageReceiver; import org.xwoot.jxta.Peer; import org.xwoot.jxta.NetworkManager.ConfigMode; import org.xwoot.jxta.message.Message; import org.xwoot.jxta.message.MessageFactory; import org.xwoot.thomasRuleEngine.ThomasRuleEngine; import org.xwoot.thomasRuleEngine.ThomasRuleEngineException; import org.xwoot.thomasRuleEngine.core.Entry; import org.xwoot.thomasRuleEngine.core.Identifier; import org.xwoot.thomasRuleEngine.core.Value; import org.xwoot.thomasRuleEngine.op.ThomasRuleOp; import org.xwoot.wootEngine.Patch; import org.xwoot.wootEngine.WootEngine; import org.xwoot.wootEngine.WootEngineException; import org.xwoot.wootEngine.core.WootContent; import org.xwoot.wootEngine.op.WootOp; import org.xwoot.xwootApp.core.LastPatchAndXWikiXWootId; import org.xwoot.xwootApp.core.tre.XWootObjectIdentifier; import org.xwoot.xwootApp.core.tre.XWootObjectValue; import org.xwoot.xwootUtil.FileUtil; /** * DOCUMENT ME! * * @version $Id$ */ public class XWoot3 implements XWootAPI, JxtaCastEventListener, DirectMessageReceiver { /** When the last synchronization took place */ private Date lastSynchronizationDate; /** The last synchronization failure */ private String lastSynchronizationFailure; /** The content manager providing the connection with the wiki. */ private XWootContentProviderInterface contentManager; /** The woot engine used for merging objects' content. */ private WootEngine wootEngine; /** The P2P module. */ private Peer peer; /** The Last-Writer-Win engine used to manage replicated data and objects. */ private ThomasRuleEngine tre; /** The anti-entropy module used to keep track of patches. */ private AntiEntropy antiEntropy; /** Logging component. */ private final Log logger = LogFactory.getLog(this.getClass()); private String contentManagerURL; private String stateDirPath; public static final String STATEFILENAME = "state.zip"; public static final String STATE_FILE_NAME_PREFIX = "xwootState"; public static final String STATE_FILE_EXTENSION = ".zip"; public static final String STATE_FILE_NAME_SEPARATOR = "-"; /** The working directory where to store data. */ private String workingDir; /** If this peer created the group he currently is member of. */ private boolean createdCurrentGroup; /** * A content id list. ContentManager adds an id when a content change occurs. (see {@link LastPatchAndXWikiXWootId} * ) */ private LastPatchAndXWikiXWootId lastModifiedContentIdMap; private String contentProviderLogin; private String contentProviderPassword; /** * Creates a new XWoot object. * * @param contentManager DOCUMENT ME! * @param wootEngine DOCUMENT ME! * @param peer DOCUMENT ME! * @param clock DOCUMENT ME! * @param siteUrl DOCUMENT ME! * @param siteId DOCUMENT ME! * @param tre DOCUMENT ME! * @param ae TODO * @param WORKINGDIR DOCUMENT ME! * @throws WootEngineException * @throws XWootContentProviderException */ public XWoot3(XWootContentProviderInterface contentManager, WootEngine wootEngine, Peer peer, String workingDir, ThomasRuleEngine tre, AntiEntropy ae, String contentProviderLogin, String contentProviderPassword) throws XWootException { // TODO: Remove parameter peerId and siteId. They will be replaced with jxta values. this.lastModifiedContentIdMap = new LastPatchAndXWikiXWootId(workingDir); this.workingDir = workingDir; this.stateDirPath = workingDir + File.separator + "stateDir"; this.createWorkingDir(); //String endpoint="http://concerto1:8080/xwiki/xmlrpc"; this.contentManager = contentManager; this.contentProviderLogin = contentProviderLogin; this.contentProviderPassword = contentProviderPassword; //TODO this.contentManagerURL = ""; this.wootEngine = wootEngine; this.peer = peer; this.tre = tre; // FIXME: Focus on replacing siteId with the peer's id (UUID). Modify TreEngine and WootEngine and provide UUID operations (subtraction and/or comparison). //this.siteId = siteId; //this.peerId = peerId; this.logger.info(this.getXWootName() + " : AntiEntropy component created."); this.antiEntropy = ae; this.logger.info( this.getXWootName() + " : XWoot engine created. XWoot working directory : " + workingDir + "\n\n"); //this.p2Pconnected = false; if (this.peer.isConnectedToNetwork()) { this.peer.stopNetwork(); } this.setGroupCreator(false); } public void clearWorkingDir() { File stateDirFile = new File(this.stateDirPath); if (stateDirFile.exists()) { FileUtil.deleteDirectory(stateDirFile); } } public void clearBaseDir() throws XWootException { File f = new File(this.workingDir); if (f.exists()) { this.logger.info(this.getXWootName() + " Delete working dir xwoot : " + f.toString()); FileUtil.deleteDirectory(f); } this.createWorkingDir(); } private void createWorkingDir() throws XWootException { File working = new File(this.workingDir); if (!working.exists() && !working.mkdir()) { throw new XWootException("Can't create xwoot directory: " + working); } if (!working.isDirectory()) { throw new RuntimeException(working + " is not a directory"); } else if (!working.canWrite()) { throw new XWootException("Can't write in directory: " + working); } File stateDirFile = new File(this.stateDirPath); if (!stateDirFile.exists() && !stateDirFile.mkdir()) { throw new XWootException("Can't create pages directory: " + stateDirFile); } } /** * DOCUMENT ME! * * @param receivedMessage DOCUMENT ME! * @throws XWootException */ public Object receiveMessage(Object aMessage) throws XWootException { if (!(aMessage instanceof Message)) { logger.warn("Not and instance of org.xwoot.jxta.Message. Dropping message."); return null; } if (!this.isConnectedToP2PGroup()) { logger.warn("Not conencted to any group. Dropping message."); return null; } Message message = (Message) aMessage; this.logger.info(this.getXWootName() + " : received message..."); this.logger.info(this.getXWootName() + " : message type : " + message.getAction()); if (!this.isStateComputed() && !message.getAction().equals(Message.Action.STATE_REPLY)) { logger.warn( "This XWoot node does not have a state for the group yet. Also, the message is not a state reply. Dropping message."); return null; } this.logger.info(this.getXWootName() + " : processing message..."); if (message.getAction().equals(Message.Action.BROADCAST_PATCH)) { this.processPatchBroadcast(message); } else if (message.getAction().equals(Message.Action.ANTI_ENTROPY_REQUEST)) { this.processAntiEntropyRequest(message); } else if (message.getAction().equals(Message.Action.ANTI_ENTROPY_REPLY)) { this.processAntiEntropyReply(message); } else if (message.getAction().equals(Message.Action.STATE_REQUEST)) { return this.processStateRequest(message); } return null; } /** * A broadcasted patch from the network. * <p> * This peer will log an apply the operations described by the patch. * * @param message the received message containing the disseminated patch. * @throws XWootException if problems occur integrating the reply in the anti-entropy log or processing the patch. */ private synchronized void processPatchBroadcast(Message message) throws XWootException { // content == Patch. try { if (!this.antiEntropy.getLog().existInLog(message.getId())) { this.logger.info(this.getXWootName() + " : received message : integration and logging."); this.antiEntropy.logMessage(message.getId(), message); this.treatePatch((Patch) message.getContent()); } else { this.logger.info(this.getXWootName() + " : received message : already in log."); } } catch (AntiEntropyException e) { throw new XWootException(this.getXWootName() + " : Problem logging the message.", e); } } /** * A reply to an anti-entropy requested by another peer. * * @param message the received message containing the requesting peer's anti-entropy log and a channel by which to reply back to him the diff. * @throws XWootException if the message does not contain information for contacting the requester, anti-entropy failed or the reply could not be set back. */ @SuppressWarnings("unchecked") private void processAntiEntropyRequest(Message message) throws XWootException { // send diff with local log this.logger.info(this.getXWootName() + " : Message asks antientropy diff -- sending it."); // Check the pipeAdv of the sender in order to be able to reply. // if (!(message.getOriginalPeerId() instanceof PipeAdvertisement)) { // throw new XWootException(this.getXWootName() + " : The message contained invalid sender identification: " + message.getOriginalPeerId() + ". Can not reply."); // } /* PipeAdvertisement pipeAdv = null; try { pipeAdv = (PipeAdvertisement) message.getOriginalPeerId(); } catch (ClassCastException cce) { throw new XWootException(this.siteId + " : The message contained invalid sender identification. Can not reply.\n", cce); }*/ // Process the sender's log and send reply. // content == messageId[]. try { // TODO: modify answerAntiEntropy or process it's result. (as stated in the todo of Message.Action.ANTI_ENTROPY_REPLY) Collection replyContent = this.antiEntropy.answerAntiEntropy(message.getContent()); this.logger.debug(this.getXWootName() + " : New message -- content : patches : result of diff beetween given log and local log -- Action : ANTI_ENTROPY_REPLY"); if (!replyContent.isEmpty()) { // We do not expect any reply for this message. this.sendMessage(replyContent, Message.Action.ANTI_ENTROPY_REPLY, message.getOriginalPeerId()); } else { this.logger.debug(this.getXWootName() + " : No anti entropy reply needed because the requesting peer has all our messages. Reply dropped."); } } catch (AntiEntropyException aee) { // TODO: can we tolerate this exception and just warn about it? throw new XWootException(this.getXWootName() + " : Problem with antiEntropy\n", aee); } catch (XWootException xe) { // just log it. this.logger.warn(this.getXWootName() + " : Failed to answer anti-entropy request to neighbor " + message.getOriginalPeerId() + "\n", xe); } try { Object[] missingIdsFromLocalLog = this.antiEntropy .getMessageIdsMissingFromLocalLog(message.getContent()); // If we have missing messages, get them from the peer that has them. if (missingIdsFromLocalLog != null && missingIdsFromLocalLog.length != 0) { // Message missingMessages = this.sendMessage(missingIdsFromLocalLog, Message.Action.MESSAGES_REQUEST, message.getOriginalPeerId()); // this.processAntiEntropyReply(missingMessages); this.logger.debug(this.getXWootName() + " : The remote peer has messages we don't have. Sending anti-entropy request."); //this.doAntiEntropy(message.getOriginalPeerId()); // If we missed some messages, they may be more. // TODO: any issues with this vs doAntiEntropy(neighbor)? this.doAntiEntropyWithAllNeighbors(); } } catch (AntiEntropyException aee) { // just log it. this.logger.warn( this.getXWootName() + " : Failed to compute the missing messages from the local log.\n", aee); } catch (XWootException xe) { // just log it. It's quite bad, but let`s hope we`ll get them next time. this.logger.warn(this.getXWootName() + " : Failed to send anti-entropy request to get the missing messages from the remote peer.\n", xe); } } /** * Send a message to a single peer by using a specified channel. * * @param content the content to send. * @param action the action that creates the message to send. Must not be null. * @param channel the channel to use to send the message. If this is null, the message will be sent to a random neighbor in the current group. * @throws XWootException if failed to send the message an invalid channel was supplied or an invalid reply was received. */ private Message sendMessage(Object content, Message.Action action, Object channel) throws XWootException { this.logger.debug("Sending a " + action + " message."); Message toSend = this.createMessage(content, action); Object reply = null; if (channel == null) { // Send to random peer in group. try { reply = this.peer.sendObjectToRandomPeerInGroup(toSend, true); } catch (Exception e) { this.logger.error("Failed to send message to random peer.\n", e); throw new XWootException("Failed to send message to random peer.\n", e); } } else { // Send to specified peer in group. PipeAdvertisement pipeAdv = null; try { if (channel instanceof String) { pipeAdv = createPipeAdvFromStringID((String) channel); } else { pipeAdv = (PipeAdvertisement) channel; } } catch (Exception e) { throw new XWootException(this.getXWootName() + " : Invalid or not supported channel specified.\n", e); } try { reply = this.peer.sendObject(toSend, pipeAdv); } catch (Exception e) { this.logger.error("Failed to send message to specified peer.\n", e); throw new XWootException("Failed to send message to specified peer.\n", e); } } // If a reply is received, it must be of type Message. if (reply != null && !(reply instanceof Message)) { throw new XWootException(this.getXWootName() + " : Received an invalid or not supported reply type (" + reply.getClass() + ")."); } return (Message) reply; } private Message createMessage(Object content, Message.Action action) { String originalPeerID = null; if (this.isConnectedToP2PGroup()) { originalPeerID = this.peer.getMyDirectCommunicationPipeIDAsString(); } else { originalPeerID = this.peer.getMyDirectCommunicationPipeName(); } return MessageFactory.createMessage(originalPeerID, content, action); } private PipeAdvertisement createPipeAdvFromStringID(String pipeIDAsString) throws URISyntaxException { PipeAdvertisement pipeAdv = (PipeAdvertisement) AdvertisementFactory .newAdvertisement(PipeAdvertisement.getAdvertisementType()); pipeAdv.setType(PipeService.UnicastType); PipeID pipeID = PipeID.create(new URI(pipeIDAsString)); pipeAdv.setPipeID(pipeID); return pipeAdv; } /** * A reply to an anti-entropy request this peer sent earlier. * * @param message the received message containing the reply. * @throws XWootException if problems occur integrating the reply in the anti-entropy log or processing the patches. */ @SuppressWarnings("unchecked") private synchronized void processAntiEntropyReply(Message message) throws XWootException { this.logger.info(this.getXWootName() + " : Integrate antientropy messages\n\n"); // TODO: Have a list of requests and if the reply comes without being requested, drop it? // TODO: content == should not be List<Message> but Patch[]. // For now, we'll keep List<Message> for backwards compatibility. Collection contents = (Collection) message.getContent(); for (Iterator iter = contents.iterator(); iter.hasNext();) { Message mess = (Message) iter.next(); try { if (!this.antiEntropy.getLog().existInLog(mess.getId())) { this.antiEntropy.logMessage(mess.getId(), mess); this.treatePatch((Patch) mess.getContent()); } } catch (AntiEntropyException e) { throw new XWootException(this.getXWootName() + " : Problems integrating the message", e); } } } /** * Process a request to get the state of this XWoot node. * * @param message the received message containing a channel by which to reply back the state. * @return a message containing a {@code byte[]} of the state file's data. * @throws XWootException if problems occur getting the state or sending back the reply. */ private Message processStateRequest(Message message) throws XWootException { this.logger.debug(this.getXWootName() + " : Processing a state request."); if (!this.isStateComputed()) { this.logger.warn(this.getXWootName() + " : This peer does not have a state. Can not answer the state request. Dropping request."); return null; } // Update the local zipped state before sending it so we don't send an old state. this.updateState(); byte[] stateFileData = null; try { stateFileData = FileUtils.readFileToByteArray(this.getState()); } catch (Exception e) { this.logger.error(this.getXWootName() + " : Failed to read the state file. Can not answer the state request. Dropping request."); return null; } Message stateReply = this.createMessage(stateFileData, Message.Action.STATE_REPLY); return stateReply; } private void treatePatch(Patch patch) throws XWootException { try { if (patch.getMDelements() != null) { for (Object tre_op : patch.getMDelements()) { this.getTre().applyOp((ThomasRuleOp) tre_op); } } XWootId xWootId = this.lastModifiedContentIdMap.getXwikiId(patch.getPageId()); if (xWootId == null) { xWootId = new XWootId(patch.getPageId(), patch.getTimestamp(), patch.getVersion(), patch.getMinorVersion()); } System.out.println(this.getXWootName() + " : New XWootID in treatePatch : " + xWootId); this.lastModifiedContentIdMap.add2PatchIdMap(xWootId, patch.getObjectId()); this.getWootEngine().deliverPatch(patch); } catch (WootEngineException e) { throw new XWootException("Problem with WootEngine"); } catch (ThomasRuleEngineException e) { throw new XWootException("Problem with ThomasRuleEngine"); } this.synchronize(); /*this.synchronizeFromXWikiToModel(true); this.synchronizeFromModelToXWiki();*/ } /** * Broadcast a new patch to the P2P network. * * @param newPatch the patch to send. * @throws XWootException if problems occur while logging the patch or sending it. */ private void sendNewPatch(Patch newPatch) throws XWootException { this.logger.debug(this.getXWootName() + " : Senging new patch"); Message message = this.createMessage(newPatch, Message.Action.BROADCAST_PATCH); try { // the message must be logged before we send it this.getAntiEntropy().logMessage(message.getId(), message); this.logger.debug(this.getXWootName() + " : Message logged in the local log. Sending it to the group."); if (this.isConnectedToP2PGroup()) { this.peer.sendObject(message, message.getAction().toString()); this.logger.debug(this.getXWootName() + " : Message(Patch) sent to the group."); } else { this.logger.warn(this.getXWootName() + " : [OFFLINE] Message(Patch) not sent. This peer is currently not conencted to a group."); } } catch (Exception e) { this.logger.error("Can't send the new Patch.\n", e); throw new XWootException("Can't send the new Patch.\n", e); } } private synchronized void synchronizeFromXWikiToModel(boolean inCopy, boolean generatePatches) throws XWootException { this.logger.info(this.getXWootName() + " : synchronize From XWiki To Model (" + inCopy + ", " + generatePatches + ")"); try { Set<XWootId> xwootIds = this.contentManager.getModifiedPagesIds(); System.out.println(this.getXWootName() + " : xwootIds => " + xwootIds); while (xwootIds != null && xwootIds.size() > 0) { Object[] objArray = xwootIds.toArray(); for (Object o : objArray) { XWootId id = (XWootId) o; List<XWootObject> objects = this.contentManager.getModifiedEntities(id); System.out.println(this.getXWootName() + " - " + id + " : entites => " + objects); // need some security : the id is cleared server side but // all the modifiedEntities are not already consumed... // It's important to remove the id before modifiedEntites treatment. System.out.println(this.getXWootName() + " : remove in xwiki list : " + id); this.contentManager.clearModification(id); System.out.println(this.getXWootName() + " : save version : " + id.getPageId() + " -> " + id); this.getLastModifiedContentIdMap().add2XWikiIdMap(id.getPageId(), id); //this.contentManager.clearModification(id); for (XWootObject newObject : objects) { Patch newPatch = this.synchronizeObjectFromXWikiToModel(newObject, id, inCopy); if (inCopy) { this.wootEngine.deliverPatch(newPatch); if (newPatch.getMDelements() != null) { try { for (Object tre_op : newPatch.getMDelements()) { this.getTre().applyOp((ThomasRuleOp) tre_op); } } catch (ThomasRuleEngineException e) { throw new XWootException("Problem with ThomasRuleEngine"); } } } // If this patch is not relevant for the network, send it. if (generatePatches) { this.sendNewPatch(newPatch); } } } xwootIds = this.contentManager.getModifiedPagesIds(); } } catch (XWootContentProviderException e) { throw new XWootException(e); } catch (WootEngineException e) { throw new XWootException(e); } this.logger.info(this.getXWootName() + " end of synchronize From XWiki To Model (" + inCopy + ")"); } private synchronized Patch synchronizeObjectFromXWikiToModel(XWootObject newObject, XWootId id, boolean inCopy) throws XWootException, WootEngineException { this.logger.info(this.getXWootName() + " : synchronize Object From XWiki To Model -- id : " + id + " -- Object : " + newObject); List<ThomasRuleOp> treOps = new ArrayList<ThomasRuleOp>(); String objectId = newObject.getGuid(); // TRE content ThomasRuleOp tre_op = this.synchronizeWithTRE(newObject); if (tre_op == null) { throw new XWootException("Synchronization problem !"); } treOps.add(tre_op); List<WootOp> wootOps = new ArrayList<WootOp>(); if (newObject.hasWootableFields()) { String pageName = newObject.getPageId(); for (XWootObjectField f : newObject.getFields()) { String fieldId = f.getName(); if (f.isWootable()) { String oldContent = ""; if (inCopy) { oldContent = this.getWootEngine().getContentManager().getCopyContent(pageName, objectId, fieldId); } else { oldContent = this.getWootEngine().getContentManager().getContent(pageName, objectId, fieldId); } wootOps.addAll(this.synchronizeWithWootEngine(pageName, objectId, fieldId, oldContent, (String) f.getValue(), inCopy)); } } } Patch newPatch = new Patch(wootOps, treOps, id.getPageId(), objectId, id.getTimestamp(), id.getVersion(), id.getMinorVersion()); this.logger.info( this.getXWootName() + " end of synchronize Object From XWiki To Model -- return : " + newPatch); return newPatch; } private Value loadObjectFromModel(String pageName, String objectId) throws XWootException { this.logger.info(this.getXWootName() + " : load Object From Model -- pagename : " + pageName + " -- id : " + objectId); XWootObjectValue obj_tre = null; XWootObject result = null; XWootObjectIdentifier id_tre = new XWootObjectIdentifier(objectId); try { obj_tre = (XWootObjectValue) this.tre.getValue(id_tre); if (obj_tre == null) { throw new XWootException( "Problem with last modified content id list -- An id is in the list but not in the Thomas Rule Engine model."); } result = (XWootObject) obj_tre.get(); for (XWootObjectField f : result.getFields()) { String fieldId = f.getName(); if (f.isWootable()) { String content = this.wootEngine.getContentManager().getContent(pageName, objectId, fieldId); obj_tre.setObjectField(new XWootObjectField(fieldId, content, true)); } } } catch (WootEngineException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ThomasRuleEngineException e) { // TODO Auto-generated catch block e.printStackTrace(); } this.logger.info(this.getXWootName() + " : end of load Object From Model -- return : " + obj_tre); return obj_tre; } private void synchronizeFromModelToXWiki() throws XWootException { this.logger.info(this.getXWootName() + " : synchronize From Model To XWiki"); Map<XWootId, Set<String>> currentList = this.lastModifiedContentIdMap.getCurrentPatchIdMap(); System.out.println(this.getXWootName() + " : current Pach id list : " + currentList); for (XWootId xwid : currentList.keySet()) { for (String objectId : currentList.get(xwid)) { Value objectValue = this.loadObjectFromModel(xwid.getPageId(), objectId); XWootObject xwootObject = (XWootObject) objectValue.get(); try { if (this.synchronizeObjectFromModelToXWiki(xwootObject)) { for (XWootObjectField f : xwootObject.getFields()) { if (f.isWootable()) { this.getWootEngine().getContentManager().copyWootContent(xwootObject.getPageId(), xwootObject.getGuid(), f.getName()); } } System.out.println(this.getXWootName() + " : remove in xwoot2 list : " + xwid); this.lastModifiedContentIdMap.removePatchId(xwid, objectId); } } catch (WootEngineException e) { throw new XWootException(e); } } } this.logger.info(this.getXWootName() + " end of synchronize From Model To XWiki"); } private synchronized boolean synchronizeObjectFromModelToXWiki(XWootObject o2) throws XWootException { this.logger.info(this.getXWootName() + " : synchronize Object From Model To XWiki -- object : " + o2); if (this.isContentManagerConnected()) { try { XWootId id = this.getLastModifiedContentIdMap().getXwikiId(o2.getPageId()); System.out.println(this.getXWootName() + " last xwiki id saved : " + id + " -- try to store ..."); XWootId newXWootId = this.contentManager.store(o2, id); System.out.println(this.getXWootName() + " result of store : " + newXWootId); if (newXWootId != null) { this.getLastModifiedContentIdMap().add2XWikiIdMap(o2.getPageId(), newXWootId); System.out.println(this.getXWootName() + " save xwiki id : " + newXWootId); System.out.println("verif : " + this.getLastModifiedContentIdMap().getXwikiId(o2.getPageId())); } else { this.logger.info(this.getXWootName() + " : some no consummed datas for id : " + o2.getPageId() + "." + o2.getGuid()); // this.synchronize(); return false; } } catch (XWootContentProviderException e) { throw new XWootException(e); } } this.logger.info(this.getXWootName() + " end of synchronize Object From Model To XWiki"); return true; } @SuppressWarnings("unchecked") private synchronized List<WootOp> synchronizeWithWootEngine(String pageName, String objectId, String fieldId, String oldPage, String newPage, boolean inCopy) throws XWootException { BufferedReader oldContent = new BufferedReader(new StringReader(oldPage)); BufferedReader newContent = new BufferedReader(new StringReader(newPage)); Diff d = new Diff(); try { d.diff(oldContent, newContent); } catch (IOException e) { this.logger.error(this.getXWootName() + " : Problem with diff when synchronizing content", e); } List l = d.getHunks(); ListIterator lIt = l.listIterator(); List<WootOp> data = new ArrayList<WootOp>(); if (lIt.hasNext()) { try { this.wootEngine.loadClock(); } catch (ClockException e) { throw new XWootException(this.getXWootName() + " : Problem when synchronizing content", e); } WootContent page = null; try { if (inCopy) { page = this.wootEngine.getContentManager().loadWootContentCopy(pageName, objectId, fieldId); } else { page = this.wootEngine.getContentManager().loadWootContent(pageName, objectId, fieldId); } } catch (WootEngineException e) { throw new XWootException(this.getXWootName() + " : Problem when synchronizing content", e); } do { Hunk hunk = (Hunk) lIt.next(); if (hunk instanceof HunkAdd) { HunkAdd ha = (HunkAdd) hunk; Iterator it = ha.getNewContent().iterator(); int pos = ha.getLD2(); int i = -1; while (it.hasNext()) { String line = (String) it.next(); WootOp ins = null; try { ins = this.getWootEngine().insert(page, line, (pos + i)); } catch (WootEngineException e) { throw new XWootException(this.getXWootName() + " : Problem when synchronizing content", e); } data.add(ins); i++; } } else if (hunk instanceof HunkDel) { HunkDel hDel = ((HunkDel) hunk); int nbOfLine = hDel.getLF1() - hDel.getLD1() + 1; int pos = hDel.getLD2() - 1; for (int i = 0; i < nbOfLine; i++) { WootOp del = null; try { del = this.getWootEngine().delete(page, pos); } catch (WootEngineException e) { throw new XWootException(this.getXWootName() + " : Problem when synchronizing content", e); } data.add(del); } } else if (hunk instanceof HunkChange) { throw new XWootException("HunkChange might not be detected, check the jlibdiff configuration"); } } while (lIt.hasNext()); try { this.wootEngine.unloadClock(); } catch (ClockException e) { this.logger.error(this.getXWootName() + " : Problem when synchronizing content", e); } try { this.wootEngine.getContentManager().unloadWootContent(page); } catch (WootEngineException e) { this.logger.error(this.getXWootName() + " : Problem when synchronizing content", e); } } if (!data.isEmpty()) { this.logger.info( this.getXWootName() + " : " + data.size() + " operation(s) applicated to content model\n\n"); } else { this.logger.info(this.getXWootName() + " : Synchronize page content :" + pageName + " -- no diff.\n\n"); } return data; } private ThomasRuleOp synchronizeWithTRE(XWootObject o) throws XWootException { XWootObjectIdentifier tre_id = new XWootObjectIdentifier(o.getGuid()); try { XWootObjectValue tre_value = (XWootObjectValue) this.tre.getValue(tre_id); if (tre_value == null) { tre_value = new XWootObjectValue(); } if (tre_value.get() == null) { tre_value.setObject(o); } else { for (XWootObjectField f : o.getFields()) { tre_value.setObjectField(f); } } ThomasRuleOp op = this.tre.getOp(tre_id, tre_value); this.tre.applyOp(op); return op; } catch (ThomasRuleEngineException e) { throw new XWootException(e); } } public synchronized void synchronize() throws XWootException { try { this.synchronize(true); } catch (XWootException e) { lastSynchronizationFailure = e.getMessage(); throw e; } finally { lastSynchronizationDate = new Date(System.currentTimeMillis()); } lastSynchronizationFailure = "Synch OK"; } /** * DOCUMENT ME! * * @throws XWootException * @throws WootEngineException */ public synchronized void synchronize(boolean generatePatches) throws XWootException { this.logger.info(this.getXWootName() + " : Starting the synchronisation of each managed pages"); if (!this.isContentManagerConnected()) { this.logger.warn("Content manager not connected. Synchronization aborded."); return; } try { if (!this.getContentProvider().getModifiedPagesIds().isEmpty()) { this.synchronizeFromXWikiToModel(!this.lastModifiedContentIdMap.getCurrentPatchIdMap().isEmpty(), generatePatches); } else { this.logger.info("No changes in xwiki => No changes done to the model."); } } catch (XWootContentProviderException e) { throw new XWootException(e); } if (!this.getLastModifiedContentIdMap().getCurrentPatchIdMap().isEmpty()) { this.synchronizeFromModelToXWiki(); } else { this.logger.info("No changes in the model => No changes done in the xwiki."); } this.logger.info(this.getXWootName() + " : Synchronising OK."); } public boolean createNetwork() throws XWootException { // TODO: change return type to void. // TODO: Read from properties file. try { // clear any seeds/seedingUris. NetworkConfigurator networkConfig = this.peer.getManager().getConfigurator(); networkConfig.clearRelaySeedingURIs(); networkConfig.clearRelaySeeds(); networkConfig.clearRendezvousSeedingURIs(); networkConfig.clearRendezvousSeeds(); this.peer.getManager().setUseDefaultSeeds(false); this.peer.getManager().setMode(ConfigMode.RENDEZVOUS_RELAY); } catch (Exception e) { throw new XWootException(this.getXWootName() + " : Failed to initialize network.", e); } try { this.peer.startNetworkAndConnect(this, this); } catch (Exception e) { throw new XWootException(this.getXWootName() + " : Failed to start P2P network.", e); } return true; /* this.clearWorkingDir(); try { this.wootEngine.clearWorkingDir(); } catch (WootEngineException e) { this.logger.error(this.peerId + " : Problem when clearing wootEngine dir\n", e); throw new XWootException(this.peerId + " : Problem when clearing wootEngine dir\n", e); } this.tre.clearWorkingDir(); this.antiEntropy.clearWorkingDir(); // try { this.peer.clearWorkingDir(); // } catch (SenderException e) { // this.logger.error(this.peerId+" : Problem when clearing sender dir\n",e); // throw new XWootException(this.peerId+" : Problem when clearing sender dir\n",e); // } this.logger.info(this.siteId + " : all datas clears"); if (!this.isContentManagerConnected()) { this.connectToContentManager(); } return true; */ } public boolean joinNetwork(String neighborURL) throws XWootException { // TODO: change return type to void. // TODO: remove parameter. Config should be read from file. // TODO: read from properties file. // using public network for now. //this.peer.getManager().setUseDefaultSeeds(true); try { this.peer.startNetworkAndConnect(this, this); } catch (Exception e) { throw new XWootException(this.getXWootName() + " : Failed to join network.", e); } // FIXME: Rejoin group by reading from a properties file or configuration object if this is an XWoot restart. // BIG PRIORITY. return true; //throw new IllegalStateException("NOT IMPLEMENTED"); /*File s = null; if (this.isStateComputed()) { s = new File(this.getStateFilePath()); } if (!this.isConnectedToP2PNetwork()) { this.p2Pconnected = true; } if (!this.isContentManagerConnected()) { this.connectToContentManager(); } if (this.getNeighborsList().contains(neighborURL) || this.addNeighbour(neighborURL)) { if (s == null) { s = this.askState(this.getXWootPeerId(), neighborURL); if (s == null) { this.logger.warn(this.getXWootName() + " : problem to get state of neighbor : " + neighborURL); return false; } } this.importState(s); return true; } return false;*/ } /** {@inheritDoc} */ public PeerGroupAdvertisement createNewGroup(String name, String description, char[] keystorePassword, char[] groupPassword) throws XWootException { if (!this.isConnectedToP2PNetwork()) { throw new XWootException(this.getXWootName() + " : Not connected to network."); } PeerGroup newGroup = null; try { newGroup = this.peer.createNewGroup(name, description, keystorePassword, groupPassword); } catch (Exception e) { this.logger.error(this.getXWootName() + " : Failed to create new group.", e); throw new XWootException("Failed to create new group.", e); } // We created this group that we just joined. We can create a state.` this.setGroupCreator(true); return newGroup.getPeerGroupAdvertisement(); // FIXME: store the currentlyJoinedGroup in a properties file or somewhere on drive in order to automatically rejoin (with proper password) the group on a reboot. } /** {@inheritDoc} */ public void joinGroup(PeerGroupAdvertisement groupAdvertisement, char[] keystorePassword, char[] groupPassword) throws XWootException { this.joinGroup(groupAdvertisement, keystorePassword, groupPassword, false); } /** {@inheritDoc} */ public void joinGroup(PeerGroupAdvertisement groupAdvertisement, char[] keystorePassword, char[] groupPassword, boolean beRendezVous) throws XWootException { if (!this.isConnectedToP2PNetwork()) { throw new XWootException(this.getXWootName() + " : Not connected to network."); } try { this.peer.joinPeerGroup(groupAdvertisement, keystorePassword, groupPassword, beRendezVous); } catch (Exception e) { this.logger.error(this.getXWootName() + " : Failed to join the group.", e); throw new XWootException("Failed to join the group.", e); } // We joined a group so we could not have created it. // FIXME: should we keep track of the groups we created to determine if we can create a state even if we did not the first time? this.setGroupCreator(false); } public void leaveGroup() throws XWootException { try { this.peer.leavePeerGroup(); } catch (Exception e) { this.logger.error(this.getXWootName() + " : Failed to leave the group.", e); throw new XWootException("Failed to leave the group.", e); } // Forget that we created any group. // FIXME: should we keep track of the groups we created to determine if we can create a state even if we did not the first time? this.setGroupCreator(false); // FIXME: un-store the currentlyJoinedGroup in a properties file or somewhere on drive in order to disable automatic rejoin (with proper password) the of the group on a reboot. } public File getState() { if (this.isStateComputed()) { return new File(this.getStateFilePath()); } return null; } /** * DOCUMENT ME! * * @param state DOCUMENT ME! * @throws XWootException */ private void setState(File newState) throws XWootException { this.logger.debug(this.getXWootName() + " : receive state and apply"); if (newState == null) { this.logger.warn(this.getXWootName() + " : null state file received. Aborting."); return; } this.logger.debug(this.getXWootName() + " : Importing file " + newState); ZipFile state = null; try { state = new ZipFile(newState); } catch (Exception e) { this.logger.error(this.getXWootName() + " : Invalid state file. Aborting.", e); throw new XWootException("Invalid state file. Set state aborted.", e); } File wootState = null; File treState = null; File antiEntropyState = null; try { FileUtil.unzipInDirectory(state, this.stateDirPath); wootState = new File(this.stateDirPath, this.getWootEngineStateFileName()); treState = new File(this.stateDirPath, this.getTreStateFileName()); antiEntropyState = new File(this.stateDirPath, this.getAntiEntropyStateFileName()); if (!wootState.exists() || !treState.exists()) { throw new WootEngineException("Expected " + wootState + " and " + treState + " files were not found after unpacking an xwoot state."); } // Clear WootEngine data. this.getWootEngine().clearWorkingDir(); // Update WootEngine data. this.getWootEngine().setState(wootState); // Clear ThomasRuleEngine data. //this.getTre().clearWorkingDir(); String treWorkingDir = this.getTre().getWorkingDir(); FileUtil.deleteDirectory(treWorkingDir); FileUtil.checkDirectoryPath(treWorkingDir); // Update ThomasRuleEngine data. FileUtil.unzipInDirectory(treState.toString(), this.tre.getWorkingDir()); // Clean Anti-Entropy data. this.antiEntropy.clearWorkingDir(); if (antiEntropyState.exists()) { // Update Anti-Entropy data. FileUtil.unzipInDirectory(antiEntropyState.toString(), this.antiEntropy.getLog().getWorkingDirectory()); } return; } catch (WootEngineException e) { this.logger.error(this.getXWootName() + " : Problems setting woot engine state \n", e); throw new XWootException(this.getXWootName() + " : Problems setting woot engine state \n", e); } catch (Exception e) { this.logger.error(this.getXWootName() + " : Problems setting the XWoot state \n", e); throw new XWootException(this.getXWootName() + " : Problems setting the XWoot state \n", e); } finally { // delete unzipped states. if (wootState != null && wootState.exists()) { wootState.delete(); } if (treState != null && treState.exists()) { treState.delete(); } if (antiEntropyState != null && antiEntropyState.exists()) { antiEntropyState.delete(); } } } /** * DOCUMENT ME! * * @return DOCUMENT ME! */ public File computeState() throws XWootException { // TODO: make return type void. if (!this.isGroupCreator()) { throw new XWootException( "Can not create a state for an existing group. There can be only one state creation per group. Create a new group istead."); } this.updateState(); return new File(this.getStateFilePath()); } public synchronized void updateState() throws XWootException { if (!this.hasJoinedAP2PGroup()) { throw new XWootException("Not joined any group."); } if (!contentManager.isConnected()) { throw new XWootException("Can't initialize woot Storage : contentManager is not connected."); } // initialization of the state directory /*File stateDir = new File(this.stateDirPath); FileUtil.deleteDirectory(stateDir); stateDir.mkdir();*/ try { FileUtil.checkDirectoryPath(this.stateDirPath); } catch (Exception e) { throw new XWootException("The state directory can not be used to store newly created states.", e); } // Make sure the internal model is up to date and generate patches only when we are updating the state and not creating it. this.logger.debug(this.getXWootName() + " : Synchronizing with wiki."); this.synchronize(this.isStateComputed()); this.logger.debug( this.getXWootName() + " : " + (this.isStateComputed() ? "Updating" : "Creating") + " state."); // TODO: could be better handled and more consistent. List<File> xwootStateFiles = new ArrayList<File>(); // get WOOT state File wootState; try { wootState = this.getWootEngine().getState(); } catch (WootEngineException e) { this.logger.error(this.getXWootName() + " : Problem to get woot engine state \n", e); throw new XWootException(this.getXWootName() + " : Problem to get woot engine state \n", e); } if (wootState != null && wootState.exists()) { File copy0 = new File(this.stateDirPath, this.getWootEngineStateFileName()); try { wootState = FileUtil.moveFile(wootState, copy0); } catch (Exception e) { this.logger.error(this.getXWootName() + " : Failed to replace new state file with old one. State creation process failed.\n", e); throw new XWootException(this.getXWootName() + " : Failed to replace new state file with old one. State creation process failed.\n", e); } } else { this.logger.error(this.getXWootName() + " : The Woot state did not compute successfuly.\n"); throw new XWootException(this.getXWootName() + " : The Woot state did not compute successfuly."); } xwootStateFiles.add(wootState); // get TRE state File treState = new File(this.stateDirPath, this.getTreStateFileName()); try { FileUtil.zipDirectory(this.tre.getWorkingDir(), treState.toString()); } catch (Exception e) { this.logger.error(this.getXWootName() + " : The TRE state did not compute successfuly.\n", e); throw new XWootException(this.getXWootName() + " : The TRE state did not compute successfuly.\n", e); } xwootStateFiles.add(treState); File antiEntropyState = null; int logSize = 0; try { logSize = this.antiEntropy.getLog().logSize(); } catch (Exception e) { this.logger.error(this.getXWootName() + " : The Anti-entropy state did not compute successfuly.\n", e); throw new XWootException( this.getXWootName() + " : The Anti-entropy state did not compute successfuly.\n", e); } if (logSize != 0) { // get Anti-Entropy log's state antiEntropyState = new File(this.stateDirPath, this.getAntiEntropyStateFileName()); try { FileUtil.zipDirectory(this.antiEntropy.getLog().getWorkingDirectory(), antiEntropyState.toString()); } catch (Exception e) { this.logger.error(this.getXWootName() + " : The Anti-entropy state did not compute successfuly.\n", e); throw new XWootException( this.getXWootName() + " : The Anti-entropy state did not compute successfuly.\n", e); } xwootStateFiles.add(antiEntropyState); } // package the WOOT state, TRE state and Anti-Entropy log together. try { //String zip=FileUtil.zipDirectory(this.stateDirPath/*, File.createTempFile("state", ".zip").getPath()*/); FileUtil.zipFiles(xwootStateFiles.toArray(new File[0]), this.getStateFilePath()); /* I think we should not make isStateComputed() return true if we failed. File result = new File(this.getStateFilePath()); result.createNewFile(); return null; */ } catch (IOException e) { this.logger.error(this.getXWootName() + " : Problems creating the XWoot state.\n", e); throw new XWootException(this.getXWootName() + " : Problems creating the XWoot state.\n", e); } // delete the state files because they are now packed into the xwoot state. for (File componentStateFile : xwootStateFiles) { if (componentStateFile != null && componentStateFile.exists()) { componentStateFile.delete(); } } this.logger.debug(this.getXWootName() + " : Finished processing state."); } /** {@inheritDoc} */ public boolean isGroupCreator() { return createdCurrentGroup; } /** * @param createdCurrentGroup if this peer created the group this peer is a member of. * @see #isGroupCreator() */ public void setGroupCreator(boolean createdCurrentGroup) { this.createdCurrentGroup = createdCurrentGroup; } private String getWootEngineStateFileName() { /*StringBuilder result = new StringBuilder(); result.append(WootEngine.STATE_FILE_NAME_PREFIX); result.append(STATE_FILE_NAME_SEPARATOR); result.append(this.peer.getCurrentJoinedPeerGroup().getPeerGroupID().toString()); result.append(WootEngine.STATE_FILE_EXTENSION); return result.toString();*/ return WootEngine.STATE_FILE_NAME; } private String getTreStateFileName() { /*StringBuilder result = new StringBuilder(); result.append(ThomasRuleEngine.STATE_FILE_NAME_PREFIX); result.append(STATE_FILE_NAME_SEPARATOR); result.append(this.peer.getCurrentJoinedPeerGroup().getPeerGroupID().toString()); result.append(ThomasRuleEngine.STATE_FILE_EXTENSION); return result.toString();*/ return ThomasRuleEngine.TRE_STATE_FILE_NAME; } private String getAntiEntropyStateFileName() { return "antiEntropyState.zip"; } public boolean isStateComputed() { File result = new File(this.getStateFilePath()); return result.exists(); } public boolean importState(File stateFileToImport) throws XWootException { // TODO: make return type void. if (stateFileToImport == null) { this.logger.warn(this.getXWootName() + " : Tried to import a null state. Operation ignored."); return false; } if (!this.isContentManagerConnected()) { this.connectToContentManager(); } File currentState = new File(this.getStateFilePath()); // set the state this.setState(stateFileToImport); // Mark the wiki as not modified because we just imported a state and have to override the wiki with the state's model. try { this.contentManager.getModifiedPagesIds(); this.contentManager.clearAllModifications(); } catch (Exception e) { this.logger.error(this.getXWootPeerId() + " : Failed to mark the xwiki as not modified before synchronizing with it.", e); throw new XWootException(this.getXWootPeerId() + " : Failed to mark the xwiki as not modified before synchronizing with it.", e); } // FIXME: do some preprocessing of the entries and align them in a map from pageID to list of page objects. // to reduce complexity try { List<Entry> entries = this.tre.getAllEntries(); for (Entry pageEntry : entries) { Identifier id = pageEntry.getId(); String objectId = id.getId(); if (!objectId.startsWith("page:")) { // Skip objects that don't describe an XWiki page. continue; } this.logger.debug("Synchronizing page from model: " + objectId); String pageId = objectId.substring(objectId.indexOf(":") + 1); // Update/Create the page first before doing the same for page objects. Value pageObjectValue = pageEntry.getValue(); XWootObject pageObject = (XWootObject) pageObjectValue.get(); XWootId pageXWootID = this.contentManager.store(pageObject); // Update/Create page objects. for (Entry objectEntry : entries) { Identifier idOfObjectEntry = objectEntry.getId(); if (idOfObjectEntry.getId().startsWith("page:")) { // Skip page-entries. continue; } XWootObject objectInPage = (XWootObject) objectEntry.getValue().get(); String pageIdOfObjectEntry = objectInPage.getPageId(); if (!pageIdOfObjectEntry.equals(pageId)) { // Skip entries that are not for our current page. continue; } this.logger.debug("Synchronizing object from model: " + objectId); // Store the object. pageXWootID = this.contentManager.store(objectInPage); } // Save it's last known version as fix for http://jira.xwiki.org/jira/browse/CONCERTO-21 this.getLastModifiedContentIdMap().add2XWikiIdMap(pageId, pageXWootID); } } catch (ThomasRuleEngineException tree) { this.logger.error(this.getXWootPeerId() + " : Failed to get objects from the XWoot model.\n", tree); throw new XWootException(this.getXWootPeerId() + " : Failed to get objects from the XWoot model.\n", tree); } catch (XWootContentProviderException xwcpe) { this.logger.error( this.getXWootPeerId() + " : Failed to update XWiki with objects from the XWoot model.\n", xwcpe); throw new XWootException( this.getXWootPeerId() + " : Failed to update XWiki with objects from the XWoot model.\n", xwcpe); } // If it is not a re-import of an existing state, then copy it and make it the current state. if (/*!stateFileToImport.equals(currentState) || */!stateFileToImport.getParent().toString() .equals(this.workingDir)) { try { FileUtil.copyFile(stateFileToImport.toString(), currentState.toString()); } catch (IOException e) { this.logger.error(this.getXWootName() + " : Problem when copying state file ", e); throw new XWootException(this.getXWootName() + " : Problem when copying state file ", e); } } return true; } public File askStateToGroup() throws XWootException { if (!this.isConnectedToP2PGroup()) { throw new XWootException(this.getXWootName() + " : Failed to ask the state bacause there is no other group member connected."); } this.logger.debug(this.getXWootName() + " : Asking the XWoot state to the current group."); Message stateReply = null; try { stateReply = this.sendMessage(null, Message.Action.STATE_REQUEST, null); if (stateReply == null) { throw new XWootException("A peer was contacted but it did not send back a reply."); } } catch (XWootException e) { this.logger.error(this.getXWootName() + " : Failed to get the state from the current group.\n", e); throw new XWootException(this.getXWootName() + " : Failed to get the state from the current group.\n", e); } if (!(stateReply.getAction().equals(Message.Action.STATE_REPLY))) { this.logger.error(this.getXWootName() + " : Invalid state reply message action (" + stateReply.getAction() + ")."); throw new XWootException(this.getXWootName() + " : Invalid state reply message action (" + stateReply.getAction() + ")."); } byte[] stateFileData = null; try { stateFileData = (byte[]) stateReply.getContent(); } catch (ClassCastException cce) { throw new XWootException(this.getXWootName() + " : Invalid state reply message content.\n", cce); } File tempStateFile = null; try { tempStateFile = File.createTempFile("receivedXWootState", ".zip"); FileUtils.writeByteArrayToFile(tempStateFile, stateFileData); } catch (Exception e) { throw new XWootException("Failed to write the received state file to drive.\n", e); } return tempStateFile; } public void doAntiEntropyWithAllNeighbors() throws XWootException { /*if (this.isConnectedToP2PNetwork()) { Collection c = this.getNeighborsList(); if ((c == null) || c.isEmpty()) { return; } Iterator i = c.iterator(); while (i.hasNext()) { this.doAntiEntropy((String) i.next()); } }*/ if (!this.isConnectedToP2PGroup()) { this.logger.warn(this.getXWootName() + " : Not successfuly joined or connected to a P2P group yet."); return; } this.logger.info(this.getXWootName() + " : Asking antiEntropy with all neighbors."); Object content = null; try { content = this.antiEntropy.getMessageIdsForAskAntiEntropy(); } catch (AntiEntropyException e) { this.logger.error(this.getXWootName() + " : Problems getting content for antiEntropy.\n", e); throw new XWootException(this.getXWootName() + " : Problems getting content for antiEntropy.\n", e); } Message message = this.createMessage(content, Message.Action.ANTI_ENTROPY_REQUEST); this.logger.debug( this.getXWootName() + " : New message -- content : log patches -- Action : " + message.getAction()); try { this.peer.sendObject(message, message.getAction().toString()); } catch (Exception e) { throw new XWootException(this.getXWootName() + " : Can't do anti-entropy with all neighbors.\n", e); } } /** * Request anti-entropy from a specific neighbor. * * @param neighbor the neighbor to contact. * @throws XWootException if problems occur getting the local message log or while sending the message. * @throws InvalidParameterException if the given neighbor is not of the correct type. * @see #sendMessage(Object, org.xwoot.jxta.message.Message.Action, Object) */ public void doAntiEntropy(Object neighbor) throws XWootException { if (!this.isConnectedToP2PGroup()) { this.logger.warn(this.getXWootName() + " : Not successfuly joined or connected to a P2P group yet."); return; } /*if (!(neighbor instanceof PipeAdvertisement)) { throw new InvalidParameterException("Parameter must be of type " + PipeAdvertisement.class + ". Provided: " + neighbor); }*/ this.logger.info(this.getXWootName() + " : Asking antiEntropy with a specific neighbor."); this.logger.info(this.getXWootName() + " : PipeID of neighbor: " + neighbor); String neighborPipeId = (String) neighbor; PipeAdvertisement neighborPipeAdvertisement = null; try { neighborPipeAdvertisement = this.createPipeAdvFromStringID(neighborPipeId); } catch (Exception e) { this.logger.error("Invalid neighbor. Could not create communication channel."); throw new XWootException("Invalid neighbor. Could not create communication channel.\n", e); } Object content = null; try { content = this.antiEntropy.getMessageIdsForAskAntiEntropy(); } catch (AntiEntropyException e) { this.logger.error(this.getXWootName() + " : Problems getting content for antiEntropy.\n", e); throw new XWootException(this.getXWootName() + " : Problems getting content for antiEntropy.\n", e); } this.logger.debug(this.getXWootName() + " : New message -- content : log patches -- Action : " + Message.Action.ANTI_ENTROPY_REQUEST.toString()); // The reply will come as a separate message, initiated by the destination peer. This is caused by the broadcast nature of ANTI_ENTROPY_REQUEST messages. this.sendMessage(content, Message.Action.ANTI_ENTROPY_REQUEST, neighborPipeAdvertisement); } public void connectToContentManager() throws XWootException { this.logger.info("Connect to content manager."); if (!this.isContentManagerConnected()) { try { this.logger.info(this.getXWootName() + " : Connect to content provider "); this.contentManager.login(this.contentProviderLogin, this.contentProviderPassword); } catch (XWootContentProviderException e) { throw new XWootException("Problem with login", e); } this.logger.info("Content manager connected."); } else { this.logger.debug("Content manager already connected."); } } public boolean isContentManagerConnected() { return contentManager.isConnected(); } public void disconnectFromContentManager() throws XWootException { this.logger.info(this.getXWootName() + " : Disconnect from content provider "); this.contentManager.logout(); this.logger.info(this.getXWootName() + " : Disconnected from content provider "); } public void reconnectToP2PNetwork() throws XWootException { this.logger.info(this.getXWootName() + " : (Re)Connect to P2P Network."); if (!this.isConnectedToP2PNetwork()) { // TODO: rejoining of the group will be done in joinNetwork(). this.joinNetwork(null); } else { this.logger.warn(this.getXWootName() + " : Already connected to P2P Network."); } this.logger.info(this.getXWootName() + " : (Re)Connected to P2P Network."); } public boolean isConnectedToP2PGroup() { return this.peer.isConnectedToGroup(); } /** {@inheritDoc} */ public boolean hasJoinedAP2PGroup() { return this.peer.hasJoinedAGroup(); } public boolean isConnectedToP2PNetwork() { //return this.p2Pconnected; return this.peer.isConnectedToNetwork(); } public void disconnectFromP2PNetwork() throws XWootException { this.logger.info(this.getXWootName() + " : Disconnect from P2P Network."); if (this.isConnectedToP2PNetwork()) { if (this.isContentManagerConnected()) { try { this.synchronize(); } catch (Exception e) { // just log it. this.logger.warn( this.getXWootName() + " : Failed to synchronize before disconnecting from network.", e); } } // FIXME: anti-entropy with all neighbors is quite useless if we immediately disconnect because it's asynchronously designed. try { this.doAntiEntropyWithAllNeighbors(); } catch (Exception e) { // just log it. this.logger.warn(this.getXWootName() + " : Failed to do anti-entropy with all neighbors before disconnecting from network.", e); } // We could wait a fixed amount of time before disconnecting so that we get up to date, but the users might not like it. // Another idea would be to do the actual stopNetwork() call in a thread and immediately return, so that the users don't get blocked, // but this thread would have to check before actually stopping the network if another reconnectToNetwork() call was made in between // and cancel stopping the network if this is the case. this.peer.stopNetwork(); } else { this.logger.warn(this.getXWootName() + " : Already disconnected from P2P network."); } this.logger.info(this.getXWootName() + " : Disconnected from P2P Network."); } /*public void removeNeighbor(String neighborURL) throws XWootException { try { this.peer.removeNeighbor(neighborURL); } catch (SenderException e) { this.logger.error(this.peerId + " : Problem to remove neighbor \n", e); throw new XWootException(this.peerId + " : Problem to remove neighbor \n", e); } } public boolean addNeighbour(String neighborURL) { return this.getSender().addNeighbor(this.getXWootPeerId(), neighborURL); } public boolean forceAddNeighbour(String neighborURL) { return this.getSender().addNeighbor(null, neighborURL); }*/ @SuppressWarnings("unchecked") public Collection getNeighborsList() { return Collections.list(this.peer.getKnownDirectCommunicationPipeAdvertisements()); } @SuppressWarnings("unchecked") public Collection getGroups() { return Collections.list(this.peer.getKnownGroups()); } /** * DOCUMENT ME! * * @return DOCUMENT ME! */ public AntiEntropy getAntiEntropy() { return this.antiEntropy; } /** * DOCUMENT ME! * * @return DOCUMENT ME! */ public Peer getPeer() { return this.peer; } /** * DOCUMENT ME! * * @return DOCUMENT ME! */ public ThomasRuleEngine getTre() { return this.tre; } /** * DOCUMENT ME! * * @return DOCUMENT ME! */ public WootEngine getWootEngine() { return this.wootEngine; } /** * DOCUMENT ME! * * @return DOCUMENT ME! */ public XWootContentProviderInterface getContentProvider() { return this.contentManager; } public String getContentManagerURL() { return this.contentManagerURL; } public String getXWootPeerId() { return this.peer.getMyPeerID().toString(); } public String getXWootName() { return this.peer.getMyPeerName(); } public String getStateFilePath() { return this.stateDirPath + File.separatorChar + getStateFileName(); } public String getStateFileName(PeerGroup group) { if (group == null) { throw new NullPointerException("Null peer group provided."); } return STATE_FILE_NAME_PREFIX + STATE_FILE_NAME_SEPARATOR + group.getPeerGroupID().toString() + STATE_FILE_EXTENSION; } public String getStateFileName() { if (!this.hasJoinedAP2PGroup()) { throw new IllegalStateException( "Unable to get the state for the currently joined group because this peer has not joined any group yet."); } return this.getStateFileName(this.peer.getCurrentJoinedPeerGroup()); } /** * @return the lastModifiedContentIdMap */ public LastPatchAndXWikiXWootId getLastModifiedContentIdMap() { return this.lastModifiedContentIdMap; } public List<String> getLastPages(String id) throws XWootException { // TODO Auto-generated method stub return null; } /** {@inheritDoc} **/ public void jxtaCastProgress(JxtaCastEvent event) { if (event.percentDone == 100) { if (event.transType == JxtaCastEvent.RECV) { this.logger.debug(this.getXWootName() + " : Received a broadcasted message."); if (!(event.transferedData instanceof Message)) { this.logger.warn(this.getXWootName() + " : Discarding unexpected broadcasted object of type " + event.transferedData.getClass() + " from " + event.sender + "(" + event.senderId + ")"); return; } Message message = (Message) event.transferedData; try { // Not interested in any reply. this.receiveMessage(message); } catch (XWootException e) { this.logger.error(this.getXWootName() + " : Failed to process received message.", e); return; } } else if (event.transType == JxtaCastEvent.RECV) { this.logger.debug(this.getXWootName() + " : Successfuly broadcasted the message."); } } } /** {@inheritDoc} **/ public void receiveDirectMessage(Object aMessage, ObjectOutputStream oos) { // TODO: create the class directMessageEvent and include sender. this.logger.debug("Directly received a message."); if (!(aMessage instanceof Message)) { this.logger.warn(this.getXWootName() + " : Discarding unexpected directly sent object of type " + aMessage.getClass() + "."); return; } Message message = (Message) aMessage; Object reply = null; try { reply = this.receiveMessage(message); } catch (XWootException e) { this.logger.error(this.getXWootName() + " : Failed to process received message.", e); return; } try { oos.writeObject(reply); } catch (Exception e) { this.logger.error(this.getXWootName() + " : Failed to send back the reply.", e); return; } } /** {@inheritDoc} **/ public Log getLog() { return this.logger; } /** {@inheritDoc} */ public String getWorkingDir() { return this.workingDir; } public Date getLastSynchronizationDate() { return lastSynchronizationDate; } public String getLastSynchronizationFailure() { return lastSynchronizationFailure; } }