// Copyright 2009 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.enterprise.connector.util.diffing;
import com.google.common.base.Preconditions;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Description of a change to be sent to the Search Appliance.
*
* @since 2.8
*/
public class Change {
/**
* Indication of the {@link DocumentHandleFactory} needed to
* un-serialize a {@link Change} from its JSON representation.
*/
public static enum FactoryType {
/** Indicates the client provided {@link DocumentHandleFactory} */
CLIENT,
/**
* Indicates the internal {@link DocumentHandleFactory}. Currently
* this is used for system generated deletes.
*/
INTERNAL
}
/** Enumeration of fields in the JSON representation of a {@link Change}. */
static enum Field {
FACTORY_TYPE, DOCUMENT_HANDLE, MONITOR_CHECKPOINT,
mcp //Monitor checkpoint was represented as 'mcp' in old format.
}
private final FactoryType factoryType;
private final DocumentHandle documentHandle;
private final MonitorCheckpoint monitorCheckpoint;
/**
* Create a new {@code Change}.
*
* @param factoryType
* @param documentHandle
* @param monitorCheckpoint
*/
public Change(FactoryType factoryType, DocumentHandle documentHandle,
MonitorCheckpoint monitorCheckpoint) {
Preconditions.checkNotNull(factoryType);
Preconditions.checkNotNull(documentHandle);
Preconditions.checkNotNull(monitorCheckpoint);
this.factoryType = factoryType;
this.documentHandle = documentHandle;
this.monitorCheckpoint = monitorCheckpoint;
}
/**
* Create a new {@code Change} based on a JSON-encoded object.
*
* @param json a JSON-encoded object
* @param internalFactory a DocumentHandleFactory
* @param clientFactory a DocumentHandleFactory
* @throws JSONException
*/
Change(JSONObject json, DocumentHandleFactory internalFactory,
DocumentHandleFactory clientFactory) throws JSONException {
FactoryType tempFactoryType;
DocumentHandle docHandle;
if (isNewVersionOfChangeRepresentation(json)) {
tempFactoryType = FactoryType.valueOf(json.getString(
Field.FACTORY_TYPE.name()));
if (tempFactoryType.equals(FactoryType.INTERNAL)) {
docHandle = internalFactory.fromString(
json.getString(Field.DOCUMENT_HANDLE.name()));
} else {
docHandle = clientFactory.fromString(
json.getString(Field.DOCUMENT_HANDLE.name()));
}
this.monitorCheckpoint = new MonitorCheckpoint(
json.getJSONObject(Field.MONITOR_CHECKPOINT.name()));
} else {
// TODO(jlacey): Remove this code, since FS connector 2.6 is obsolete.
// This else condition is required for previous version of diffing
// connector (FS connector) which used a completely different String
// representation of Change.There is no way to know which factory to
// use because the old json tags are not available so approach here
// is to ask question to each factory and one that give non-null handle
// will be used. This way the change will be in FS connector code since
// it knows what it used previously. Implementation classes of
// DocumentHandleFactory should take care of checking the backward
// compatibility of the change representations.
tempFactoryType = FactoryType.INTERNAL;
docHandle = internalFactory.fromString(json.toString());
if (docHandle == null) {
tempFactoryType = FactoryType.CLIENT;
docHandle = clientFactory.fromString(json.toString());
}
if (docHandle == null) {
throw new IllegalArgumentException("Could not "
+ "constitute a document handle with given Json object.");
}
this.monitorCheckpoint = new MonitorCheckpoint(
json.getJSONObject(Field.mcp.name()));
}
this.factoryType = tempFactoryType;
this.documentHandle = docHandle;
}
/**
* Returns true if the change represented by Json object is of new version
* or not.
* @param json json representation of the change
* @return true / false depending on the version of Change representation
*/
private boolean isNewVersionOfChangeRepresentation(JSONObject json) {
return json.has(Field.FACTORY_TYPE.name());
}
/**
* @return the monitor checkpoint associated with this change
*/
MonitorCheckpoint getMonitorCheckpoint() {
return monitorCheckpoint;
}
JSONObject getJson() {
JSONObject result = new JSONObject();
try {
result.put(Field.FACTORY_TYPE.name(),
factoryType.name());
result.put(Field.DOCUMENT_HANDLE.name(),
documentHandle.toString());
result.put(Field.MONITOR_CHECKPOINT.name(),
monitorCheckpoint.getJson());
return result;
} catch (JSONException e) {
// Only thrown if a key is null or a value is a non-finite number, neither
// of which should ever happen.
throw new RuntimeException("internal error: failed to create JSON", e);
}
}
DocumentHandle getDocumentHandle() {
return documentHandle;
}
/**
* Converts this instance into a JSON object and
* returns that object's string representation.
*/
@Override
public String toString() {
return "" + getJson();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((documentHandle == null) ? 0 : documentHandle.hashCode());
result = prime * result
+ ((factoryType == null) ? 0 : factoryType.hashCode());
result = prime * result
+ ((monitorCheckpoint == null) ? 0 : monitorCheckpoint.hashCode());
return result;
}
@Override
// TODO: Upgrade CheckpointAndChangeQueueTest to not depend on
// Change implementing values equality which is not
// reliable given DocumentHandle may fall back to
// object instance equality.
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Change other = (Change) obj;
if (documentHandle == null) {
if (other.documentHandle != null) {
return false;
}
} else if (!documentHandle.equals(other.documentHandle)) {
return false;
}
if (factoryType == null) {
if (other.factoryType != null) {
return false;
}
} else if (!factoryType.equals(other.factoryType)) {
return false;
}
if (monitorCheckpoint == null) {
if (other.monitorCheckpoint != null) {
return false;
}
} else if (!monitorCheckpoint.equals(other.monitorCheckpoint)) {
return false;
}
return true;
}
}