/* * Funambol is a mobile platform developed by Funambol, Inc. * Copyright (C) 2010 Funambol, Inc. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License version 3 as published by * the Free Software Foundation with the addition of the following permission * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED * WORK IN WHICH THE COPYRIGHT IS OWNED BY FUNAMBOL, FUNAMBOL DISCLAIMS THE * WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. * * This program 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 General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License * along with this program; if not, see http://www.gnu.org/licenses or write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA. * * You can contact Funambol, Inc. headquarters at 643 Bair Island Road, Suite * 305, Redwood City, CA 94063, USA, or at email address info@funambol.com. * * The interactive user interfaces in modified sourceName and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License * version 3, these Appropriate Legal Notices must retain the display of the * "Powered by Funambol" logo. If the display of the logo is not reasonably * feasible for technical reasons, the Appropriate Legal Notices must display * the words "Powered by Funambol". */ package com.funambol.syncml.spds; import java.util.Hashtable; import java.util.Enumeration; import java.util.Vector; import java.io.IOException; import com.funambol.syncml.protocol.SyncML; import com.funambol.syncml.protocol.SyncMLStatus; import com.funambol.storage.StringKeyValueStore; import com.funambol.storage.StringKeyValueStoreFactory; import com.funambol.sync.SyncReport; import com.funambol.util.StringUtil; import com.funambol.util.DateUtil; import com.funambol.util.Log; /** * A class that saves and retrieves the mapping information from the store */ public class SyncStatus implements SyncReport { private static final String TAG_LOG = "SyncStatus"; private static final String SYNC_STATUS_TABLE_PREFIX = "syncstatus_"; public static final int INIT_PHASE = 0; public static final int SENDING_PHASE = 1; public static final int RECEIVING_PHASE = 2; public static final int MAPPING_PHASE = 3; private static final String REQUESTED_SYNC_MODE_KEY = "REQUESTED_SYNC_MODE"; private static final String ALERTED_SYNC_MODE_KEY = "ALERTED_SYNC_MODE"; private static final String SYNC_PHASE_KEY = "SYNC_PHASE"; private static final String SENT_ITEM_KEY = "SENT_ITEM_"; private static final String RECEIVED_ITEM_KEY = "RECEIVED_ITEM_"; private static final String INTERRUPTED_KEY = "INTERRUPTED"; private static final String SESSIOND_ID_KEY = "SESSION_ID"; private static final String STATUS_CODE_KEY = "STATUS_CODE"; private static final String LAST_SYNC_START_TIME_KEY= "LAST_SYNC_START_TIME"; private static final String LOC_URI_KEY = "LOC_URI"; private static final String REMOTE_URI_KEY = "REMOTE_URI"; private static final String TRUE = "TRUE"; private static final String FALSE = "FALSE"; private String sourceName; private StringKeyValueStore store; private int requestedSyncMode = -1; private int alertedSyncMode = -1; private String sessionId = null; private int oldRequestedSyncMode = -1; private int oldAlertSyncMode = -1; private String oldSessionId = null; private int statusCode = -1; private int oldStatusCode = -1; private Throwable se = null; private String locUri = null; private String oldLocUri = null; private String remoteUri = null; private String oldRemoteUri = null; private long lastSyncStartTime = 0; private long oldLastSyncStartTime = 0; private boolean interrupted = false; private boolean oldInterrupted = false; private long startTime = 0; private long endTime = 0; private Hashtable sentItems = new Hashtable(); private Hashtable receivedItems = new Hashtable(); private Hashtable pendingSentItems = new Hashtable(); private Hashtable pendingReceivedItems = new Hashtable(); private Vector sentResumedItems = new Vector(); private Vector receivedResumedItems = new Vector(); private int initialReceivedAddNumber = 0; private int initialReceivedReplaceNumber = 0; private int initialReceivedDeleteNumber = 0; private int initialSentAddNumber = 0; private int initialSentReplaceNumber = 0; private int initialSentDeleteNumber = 0; private static StringKeyValueStoreFactory storeFactory = StringKeyValueStoreFactory.getInstance(); public SyncStatus(String sourceName) { this.sourceName = sourceName; store = storeFactory.getStringKeyValueStore(SYNC_STATUS_TABLE_PREFIX + sourceName); } public int getRequestedSyncMode() { return requestedSyncMode; } public void setRequestedSyncMode(int requestedSyncMode) { oldRequestedSyncMode = this.requestedSyncMode; this.requestedSyncMode = requestedSyncMode; } public long getLastSyncStartTime() { return lastSyncStartTime; } public void setLastSyncStartTime(long lastSyncStartTime) { oldLastSyncStartTime = this.lastSyncStartTime; this.lastSyncStartTime = lastSyncStartTime; } public int getAlertedSyncMode() { return alertedSyncMode; } public void setAlertedSyncMode(int alertedSyncMode) { oldAlertSyncMode = this.alertedSyncMode; this.alertedSyncMode = alertedSyncMode; } public int getStatusCode() { return statusCode; } public void setStatusCode(int statusCode) { oldStatusCode = this.statusCode; this.statusCode = statusCode; } public void setSessionId(String sessionId) { oldSessionId = this.sessionId; this.sessionId = sessionId; } public String getSessionId() { return sessionId; } public void setLocUri(String locUri) { oldLocUri = this.locUri; this.locUri = locUri; } public String getLocUri() { return locUri; } public void setRemoteUri(String remoteUri) { oldRemoteUri = this.remoteUri; this.remoteUri = remoteUri; } public String getRemoteUri() { return remoteUri; } public void addSentItem(String key, String cmd) { // The item was sent but a status has not been received yet SentItemStatus status = new SentItemStatus(cmd); pendingSentItems.put(key, status); } public int getSentItemsCount() { return sentItems.size() + pendingSentItems.size(); } public Enumeration getSentItems() { if (pendingSentItems.size() == 0) { return sentItems.keys(); } else if (sentItems.size() == 0) { return pendingSentItems.keys(); } else { // This should never happen with our SyncManager, but we support the // case as well Vector res = new Vector(); Enumeration e = sentItems.keys(); while(e.hasMoreElements()) { String k = (String)e.nextElement(); res.addElement(k); } e = pendingSentItems.keys(); while(e.hasMoreElements()) { String k = (String)e.nextElement(); res.addElement(k); } return res.elements(); } } /** * The client received the status for an item it sent out. */ public void receivedItemStatus(String key, int status) { SentItemStatus itemStatus = (SentItemStatus)sentItems.get(key); if (itemStatus == null) { itemStatus = (SentItemStatus)pendingSentItems.get(key); } if (itemStatus == null) { Log.error(TAG_LOG, "Setting the status for an item which was not sent " + key); } else { itemStatus.setStatus(status); } } /** * The client received an item via a command and it has been processed * generating a certain status. * * @param guid the server id for the item * @param luid the client id for the item * @param cmd the command the server sent the item into * @param status the client status for this command */ public void addReceivedItem(String guid, String luid, String cmd, int statusCode) { ReceivedItemStatus status = new ReceivedItemStatus(guid, cmd); status.setStatus(statusCode); // If the pending received items already have this key, then we add // the current item with another key. This may happen for example if two // commands have the same key. When keys are used (e.g. calendar sync) // we may receive a delete for an item and then the successive add will // reuse the same key. The status for the delete is not used for the // mappings, so we can safely change its key. We still keep track of it // so that the total number of excahnged items is correct. if(pendingReceivedItems.containsKey(luid)) { ReceivedItemStatus oldStatus = (ReceivedItemStatus)pendingReceivedItems.get(luid); pendingReceivedItems.put(luid, status); StringBuffer newKey = new StringBuffer(luid); newKey.append("bis"); pendingReceivedItems.put(newKey.toString(), oldStatus); } else { pendingReceivedItems.put(luid, status); } } public int getReceivedItemsCount() { return receivedItems.size() + pendingReceivedItems.size(); } // TODO: provide a meaningful implementation if needed public int getReceivedItemStatus(String guid) { return -1; } public void addMappingSent(String luid) { ReceivedItemStatus status = (ReceivedItemStatus)receivedItems.get(luid); if (status == null) { status = (ReceivedItemStatus)pendingReceivedItems.get(luid); } status.setMapSent(true); } public boolean getInterrupted() { return interrupted; } public void setInterrupted(boolean interrupted) { oldInterrupted = this.interrupted; this.interrupted = interrupted; } /** * Gets the status for a sent item. If the item has not been sent or its * status has not been received yet, then -1 is returned */ public int getSentItemStatus(String key) { SentItemStatus s = (SentItemStatus)sentItems.get(key); if (s == null) { s = (SentItemStatus)pendingSentItems.get(key); } if (s == null) { return ItemStatus.UNDEFINED_STATUS; } else { return s.getStatus(); } } public String getReceivedItemLuid(String guid) { Enumeration keys = receivedItems.keys(); while(keys.hasMoreElements()) { String luid = (String)keys.nextElement(); ReceivedItemStatus status = (ReceivedItemStatus)receivedItems.get(luid); String g = status.getGuid(); if (guid.equals(g)) { return luid; } } keys = pendingReceivedItems.keys(); while(keys.hasMoreElements()) { String luid = (String)keys.nextElement(); ReceivedItemStatus status = (ReceivedItemStatus)pendingReceivedItems.get(luid); String g = status.getGuid(); if (guid.equals(g)) { return luid; } } return null; } public Hashtable getPendingMappings() { // Create an enumeration with all the pending mappings. The value is Hashtable res = new Hashtable(); Enumeration keys = receivedItems.keys(); while(keys.hasMoreElements()) { String luid = (String)keys.nextElement(); ReceivedItemStatus status = (ReceivedItemStatus)receivedItems.get(luid); if (!status.getMapSent() && SyncML.TAG_ADD.equals(status.getCmd())) { res.put(luid, status.getGuid()); } } keys = pendingReceivedItems.keys(); while(keys.hasMoreElements()) { String luid = (String)keys.nextElement(); ReceivedItemStatus status = (ReceivedItemStatus)pendingReceivedItems.get(luid); if (!status.getMapSent() && SyncML.TAG_ADD.equals(status.getCmd())) { res.put(luid, status.getGuid()); } } return res; } /** * Returns the ItemMap related to the given name * * @param sourceName the name of the source to be retrieved * @return ItemMap of the given source */ public void load() throws IOException { store.load(); Enumeration keys = store.keys(); while(keys.hasMoreElements()) { String key = (String)keys.nextElement(); String value = store.get(key); if (REQUESTED_SYNC_MODE_KEY.equals(key)) { requestedSyncMode = Integer.parseInt(value); } else if (ALERTED_SYNC_MODE_KEY.equals(key)) { alertedSyncMode = Integer.parseInt(value); } else if (INTERRUPTED_KEY.equals(key)) { interrupted = TRUE.equals(value.toUpperCase()); } else if (SESSIOND_ID_KEY.equals(key)) { sessionId = value; } else if (key.startsWith(SENT_ITEM_KEY)) { String itemKey = key.substring(SENT_ITEM_KEY.length()); // The value contains both the cmd and the status String values[] = StringUtil.split(value, ","); String cmd = values[0]; int status = Integer.parseInt(values[1]); SentItemStatus v = new SentItemStatus(cmd); v.setStatus(status); sentItems.put(itemKey, v); } else if (key.equals(LOC_URI_KEY)) { locUri = value; } else if (key.equals(REMOTE_URI_KEY)) { remoteUri = value; } else if (key.startsWith(RECEIVED_ITEM_KEY)) { String itemKey = key.substring(RECEIVED_ITEM_KEY.length()); // The value contains the GUID, the map flag, the cmd and the // status String values[] = StringUtil.split(value, ","); String guid = values[0]; String mapped = values[1]; String cmd = values[2]; String stat = values[3]; ReceivedItemStatus status = new ReceivedItemStatus(guid, cmd); if (TRUE.equals(mapped.toUpperCase())) { status.setMapSent(true); } else { status.setMapSent(false); } int s = Integer.parseInt(stat); status.setStatus(s); receivedItems.put(itemKey, status); } } // Keep track of how many items have been exchanged so far initialReceivedAddNumber = getTotalReceivedAddNumber(); initialReceivedReplaceNumber = getTotalReceivedReplaceNumber(); initialReceivedDeleteNumber = getTotalReceivedDeleteNumber(); initialSentAddNumber = getTotalSentAddNumber(); initialSentReplaceNumber = getTotalSentReplaceNumber(); initialSentDeleteNumber = getTotalSentDeleteNumber(); } /** * Replace the current mappings with the new one and persist the info * * @param mappings the mapping hshtable */ public void save() throws IOException { // We save only what changed, nothing more Enumeration keys = pendingSentItems.keys(); while(keys.hasMoreElements()) { String key = (String)keys.nextElement(); SentItemStatus status = (SentItemStatus)pendingSentItems.get(key); StringBuffer v = new StringBuffer(); v.append(status.getCmd()).append(",").append(status.getStatus()); store.add(SENT_ITEM_KEY + key, v.toString()); // Now move this item into the in memory values sentItems.put(key, status); } keys = pendingReceivedItems.keys(); while(keys.hasMoreElements()) { String key = (String)keys.nextElement(); ReceivedItemStatus status = (ReceivedItemStatus)pendingReceivedItems.get(key); StringBuffer v = new StringBuffer(status.getGuid()); v.append(",").append(status.getMapSent() ? TRUE : FALSE); v.append(",").append(status.getCmd()); v.append(",").append(status.getStatus()); store.add(RECEIVED_ITEM_KEY + key, v.toString()); // Now move this item into the in memory values receivedItems.put(key, status); } if (oldRequestedSyncMode != requestedSyncMode) { if (oldRequestedSyncMode == -1) { store.add(REQUESTED_SYNC_MODE_KEY, "" + requestedSyncMode); } else { store.update(REQUESTED_SYNC_MODE_KEY, "" + requestedSyncMode); } oldRequestedSyncMode = requestedSyncMode; } if (oldAlertSyncMode != alertedSyncMode) { if (oldAlertSyncMode == -1) { store.add(ALERTED_SYNC_MODE_KEY, "" + alertedSyncMode); } else { store.update(ALERTED_SYNC_MODE_KEY, "" + alertedSyncMode); } oldAlertSyncMode = alertedSyncMode; } if (oldSessionId != sessionId) { if (oldSessionId == null) { store.add(SESSIOND_ID_KEY, sessionId); } else { store.update(SESSIOND_ID_KEY, sessionId); } oldSessionId = sessionId; } if (locUri != oldLocUri) { if (oldLocUri == null) { store.add(LOC_URI_KEY, locUri); } else { store.update(LOC_URI_KEY, locUri); } oldLocUri = locUri; } if (remoteUri != oldRemoteUri) { if (oldRemoteUri == null) { store.add(REMOTE_URI_KEY, remoteUri); } else { store.update(REMOTE_URI_KEY, remoteUri); } oldRemoteUri = remoteUri; } if (oldStatusCode != statusCode) { if (oldStatusCode == -1) { store.add(STATUS_CODE_KEY, "" + statusCode); } else { store.update(STATUS_CODE_KEY, "" + statusCode); } oldStatusCode = statusCode; } if (oldLastSyncStartTime != lastSyncStartTime) { if (oldLastSyncStartTime == 0) { store.add(LAST_SYNC_START_TIME_KEY, "" + lastSyncStartTime); } else { store.update(LAST_SYNC_START_TIME_KEY, "" + lastSyncStartTime); } oldLastSyncStartTime = lastSyncStartTime; } if (interrupted != oldInterrupted) { if (store.get(INTERRUPTED_KEY) == null) { store.add(INTERRUPTED_KEY, "" + interrupted); } else { store.update(INTERRUPTED_KEY, "" + interrupted); } oldInterrupted = interrupted; } pendingSentItems.clear(); pendingReceivedItems.clear(); store.save(); } /** * Completely reset the sync status. */ public void reset() throws IOException { store.reset(); init(); } /** * Partially reset the status. This method clears the persisted info and the * set of exchanged data, but leaves intact other information such as the * sessionId, the alert code and so on. This method is meant to be used when * the server refuses the resume and we shall start exchanging from scratch. */ public void resetExchangedItems() throws IOException { store.reset(); // Reset info about the exchanged data sentItems.clear(); receivedItems.clear(); pendingSentItems.clear(); pendingReceivedItems.clear(); initialReceivedAddNumber = 0; initialReceivedReplaceNumber = 0; initialReceivedDeleteNumber = 0; initialSentAddNumber = 0; initialSentReplaceNumber = 0; initialSentDeleteNumber = 0; } public int getTotalReceivedAddNumber() { int v1 = getItemsNumber(receivedItems, SyncML.TAG_ADD); int v2 = getItemsNumber(pendingReceivedItems, SyncML.TAG_ADD); return v1 + v2; } public int getReceivedAddNumber() { return getTotalReceivedAddNumber() - initialReceivedAddNumber; } public int getTotalReceivedReplaceNumber() { int v1 = getItemsNumber(receivedItems, SyncML.TAG_REPLACE); int v2 = getItemsNumber(pendingReceivedItems, SyncML.TAG_REPLACE); return v1 + v2; } public int getReceivedReplaceNumber() { return getTotalReceivedReplaceNumber() - initialReceivedReplaceNumber; } public int getTotalReceivedDeleteNumber() { int v1 = getItemsNumber(receivedItems, SyncML.TAG_DELETE); int v2 = getItemsNumber(pendingReceivedItems, SyncML.TAG_DELETE); return v1 + v2; } public int getReceivedDeleteNumber() { return getTotalReceivedDeleteNumber() - initialReceivedDeleteNumber; } public int getTotalSentAddNumber() { int v1 = getItemsNumber(sentItems, SyncML.TAG_ADD); int v2 = getItemsNumber(pendingSentItems, SyncML.TAG_ADD); return v1 + v2; } public int getSentAddNumber() { return getTotalSentAddNumber() - initialSentAddNumber; } public int getTotalSentReplaceNumber() { int v1 = getItemsNumber(sentItems, SyncML.TAG_REPLACE); int v2 = getItemsNumber(pendingSentItems, SyncML.TAG_REPLACE); return v1 + v2; } public int getSentReplaceNumber() { return getTotalSentReplaceNumber() - initialSentReplaceNumber; } public int getTotalSentDeleteNumber() { int v1 = getItemsNumber(sentItems, SyncML.TAG_DELETE); int v2 = getItemsNumber(pendingSentItems, SyncML.TAG_DELETE); return v1 + v2; } public int getSentDeleteNumber() { return getTotalSentDeleteNumber() - initialSentDeleteNumber; } public int getNumberOfReceivedItemsWithError() { return getNumberOfItemsWithError(receivedItems) + getNumberOfItemsWithError(pendingReceivedItems); } public int getNumberOfSentItemsWithError() { return getNumberOfItemsWithError(sentItems) + getNumberOfItemsWithError(pendingSentItems); } public void addSentResumedItem(String key) { sentResumedItems.addElement(key); } public void addReceivedResumedItem(String key) { receivedResumedItems.addElement(key); } public int getReceivedResumedNumber() { return receivedResumedItems.size(); } public int getSentResumedNumber() { return sentResumedItems.size(); } public String toString() { StringBuffer res = new StringBuffer(); res.append("\n"); res.append("==================================================================\n"); res.append("| Syncrhonization report for\n"); res.append("| Local URI: ").append(locUri).append(" - Remote URI:").append(remoteUri).append("\n"); res.append("| Requested sync mode: ").append(requestedSyncMode) .append(" - Alerted sync mode:").append(alertedSyncMode).append("\n"); res.append("|-----------------------------------------------------------------\n"); res.append("| Total changes received from server\n"); res.append("|-----------------------------------------------------------------\n"); res.append("| Add: ").append(getTotalReceivedAddNumber()).append("\n"); res.append("| Replace: ").append(getTotalReceivedReplaceNumber()).append("\n"); res.append("| Delete: ").append(getTotalReceivedDeleteNumber()).append("\n"); res.append("| Total errors: ").append(getNumberOfReceivedItemsWithError()).append("\n"); res.append("|-----------------------------------------------------------------\n"); res.append("| Total changes sent to server\n"); res.append("|-----------------------------------------------------------------\n"); res.append("| Add: ").append(getTotalSentAddNumber()).append("\n"); res.append("| Replace: ").append(getTotalSentReplaceNumber()).append("\n"); res.append("| Delete: ").append(getTotalSentDeleteNumber()).append("\n"); res.append("| Total errors: ").append(getNumberOfSentItemsWithError()).append("\n"); res.append("|-----------------------------------------------------------------\n"); res.append("| Changes received from server in this sync\n"); res.append("|-----------------------------------------------------------------\n"); res.append("| Add: ").append(getReceivedAddNumber()).append("\n"); res.append("| Replace: ").append(getReceivedReplaceNumber()).append("\n"); res.append("| Delete: ").append(getReceivedDeleteNumber()).append("\n"); res.append("|-----------------------------------------------------------------\n"); res.append("| Changes sent to server in this sync\n"); res.append("|-----------------------------------------------------------------\n"); res.append("| Add: ").append(getSentAddNumber()).append("\n"); res.append("| Replace: ").append(getSentReplaceNumber()).append("\n"); res.append("| Delete: ").append(getSentDeleteNumber()).append("\n"); res.append("|-----------------------------------------------------------------\n"); res.append("| Global sync status: ").append(getStatusCode()).append("\n"); res.append("|-----------------------------------------------------------------\n"); String start = DateUtil.formatDateTimeUTC(startTime); res.append("| Sync start time: ").append(start).append("\n"); String end = DateUtil.formatDateTimeUTC(endTime); res.append("| Sync end time: ").append(end).append("\n"); long totalSecs = (endTime - startTime) / 1000; res.append("| Sync total time: ").append(totalSecs).append(" [secs]\n"); res.append("==================================================================\n"); return res.toString(); } public void setSyncException(Throwable exc) { se = exc; } public Throwable getSyncException() { return se; } public long getStartTime() { return startTime; } public void setStartTime(long startTime) { this.startTime = startTime; } public long getEndTime() { return endTime; } public void setEndTime(long endTime) { this.endTime = endTime; } /** * This method is mainly intended for testing. It allows to use a store * factory different from the platform standard one. */ public static void setStoreFactory(StringKeyValueStoreFactory factory) { SyncStatus.storeFactory = factory; } private void init(){ requestedSyncMode = -1; alertedSyncMode = -1; sessionId = null; oldRequestedSyncMode = -1; oldAlertSyncMode = -1; oldSessionId = null; statusCode = -1; oldStatusCode = -1; sentItems.clear(); receivedItems.clear(); pendingSentItems.clear(); pendingReceivedItems.clear(); sentResumedItems.removeAllElements(); receivedResumedItems.removeAllElements(); locUri = null; remoteUri = null; se = null; startTime = 0; endTime = 0; initialReceivedAddNumber = 0; initialReceivedReplaceNumber = 0; initialReceivedDeleteNumber = 0; initialSentAddNumber = 0; initialSentReplaceNumber = 0; initialSentDeleteNumber = 0; interrupted = false; oldInterrupted = false; } private int getItemsNumber(Hashtable table, String cmd) { int count = 0; Enumeration keys = table.keys(); while (keys.hasMoreElements()) { String key = (String)keys.nextElement(); ItemStatus status = (ItemStatus)table.get(key); if (cmd.equals(status.getCmd())) { count++; } } return count; } private int getNumberOfItemsWithError(Hashtable table) { int count = 0; Enumeration keys = table.keys(); while (keys.hasMoreElements()) { String key = (String)keys.nextElement(); ItemStatus status = (ItemStatus)table.get(key); if (!SyncMLStatus.isSuccess(status.getStatus())) { count++; } } return count; } private class ItemStatus { public static final int UNDEFINED_STATUS = -1; protected String cmd; protected int status = UNDEFINED_STATUS; public ItemStatus(String cmd) { this.cmd = cmd; } public void setStatus(int status) { this.status = status; } public int getStatus() { return status; } public String getCmd() { return cmd; } } private class ReceivedItemStatus extends ItemStatus { private String guid; private boolean mapSent; private int status; public ReceivedItemStatus(String guid, String cmd) { super(cmd); this.guid = guid; } public void setMapSent(boolean value) { mapSent = value; } public String getGuid() { return guid; } public boolean getMapSent() { return mapSent; } } private class SentItemStatus extends ItemStatus { public SentItemStatus(String cmd) { super(cmd); } } }