blob: 2b523cf1ed456bcc9246f016b5cbcc4c4cba7769 [file] [log] [blame]
/*
* Copyright 2010 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.web.bindery.requestfactory.shared.impl;
import com.google.web.bindery.autobean.shared.AutoBean;
import com.google.web.bindery.autobean.shared.AutoBeanCodex;
import com.google.web.bindery.autobean.shared.AutoBeanFactory;
import com.google.web.bindery.autobean.shared.AutoBeanUtils;
import com.google.web.bindery.autobean.shared.AutoBeanVisitor;
import com.google.web.bindery.autobean.shared.Splittable;
import com.google.web.bindery.requestfactory.shared.BaseProxy;
import com.google.web.bindery.requestfactory.shared.EntityProxy;
import com.google.web.bindery.requestfactory.shared.EntityProxyId;
import com.google.web.bindery.requestfactory.shared.ProxySerializer;
import com.google.web.bindery.requestfactory.shared.ProxyStore;
import com.google.web.bindery.requestfactory.shared.messages.IdMessage;
import com.google.web.bindery.requestfactory.shared.messages.OperationMessage;
import com.google.web.bindery.requestfactory.shared.messages.IdMessage.Strength;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* The default implementation of ProxySerializer.
*/
class ProxySerializerImpl extends AbstractRequestContext implements ProxySerializer {
/**
* Used internally to unwind the stack if data cannot be found in the backing
* store.
*/
private static class NoDataException extends RuntimeException {
}
private final ProxyStore store;
/**
* If the user wants to serialize a proxy with a non-persistent id (including
* ValueProxy), we'll assign a synthetic id that is local to the store being
* used.
*/
private final Map<SimpleProxyId<?>, SimpleProxyId<?>> syntheticIds =
new HashMap<SimpleProxyId<?>, SimpleProxyId<?>>();
/**
* The ids of proxies whose content has been reloaded.
*/
private final Set<SimpleProxyId<?>> restored = new HashSet<SimpleProxyId<?>>();
private final Map<SimpleProxyId<?>, AutoBean<?>> serialized =
new HashMap<SimpleProxyId<?>, AutoBean<?>>();
public ProxySerializerImpl(AbstractRequestFactory factory, ProxyStore store) {
super(factory, Dialect.STANDARD);
this.store = store;
}
public <T extends BaseProxy> T deserialize(Class<T> proxyType, String key) {
// Fast exit to prevent getOperation from throwing an exception
if (store.get(key) == null) {
return null;
}
OperationMessage op = getOperation(key);
@SuppressWarnings("unchecked")
SimpleProxyId<T> id = (SimpleProxyId<T>) getId(op);
return doDeserialize(id);
}
public <T extends EntityProxy> T deserialize(EntityProxyId<T> id) {
return doDeserialize((SimpleEntityProxyId<T>) id);
}
/**
* Replace non-persistent ids with store-local ids.
*/
@Override
public Splittable getSerializedProxyId(SimpleProxyId<?> stableId) {
return super.getSerializedProxyId(serializedId(stableId));
}
public String serialize(BaseProxy rootObject) {
final AutoBean<? extends BaseProxy> root = AutoBeanUtils.getAutoBean(rootObject);
if (root == null) {
// Unexpected, some kind of foreign implementation of the BaseProxy?
throw new IllegalArgumentException();
}
final SimpleProxyId<?> id = serializedId(BaseProxyCategory.stableId(root));
// Only persistent and synthetic ids expected
assert !id.isEphemeral() : "Unexpected ephemeral id " + id.toString();
/*
* Don't repeatedly serialize the same proxy, unless we're looking at a
* mutable instance.
*/
AutoBean<?> previous = serialized.get(id);
if (previous == null || !previous.isFrozen()) {
serialized.put(id, root);
serializeOneProxy(id, root);
root.accept(new AutoBeanVisitor() {
@Override
public void endVisit(AutoBean<?> bean, Context ctx) {
// Avoid unnecessary method call
if (bean == root) {
return;
}
if (isEntityType(bean.getType()) || isValueType(bean.getType())) {
serialize((BaseProxy) bean.as());
}
}
@Override
public void endVisitCollectionProperty(String propertyName, AutoBean<Collection<?>> value,
CollectionPropertyContext ctx) {
if (value == null) {
return;
}
if (isEntityType(ctx.getElementType()) || isValueType(ctx.getElementType())) {
for (Object o : value.as()) {
serialize((BaseProxy) o);
}
}
}
});
}
return getRequestFactory().getHistoryToken(id);
}
@Override
protected AutoBeanFactory getAutoBeanFactory() {
return getRequestFactory().getAutoBeanFactory();
}
@Override
SimpleProxyId<BaseProxy> getId(IdMessage op) {
if (Strength.SYNTHETIC.equals(op.getStrength())) {
return getRequestFactory().allocateSyntheticId(
getRequestFactory().getTypeFromToken(op.getTypeToken()), op.getSyntheticId());
}
return super.getId(op);
}
@Override
<Q extends BaseProxy> AutoBean<Q> getProxyForReturnPayloadGraph(SimpleProxyId<Q> id) {
AutoBean<Q> toReturn = super.getProxyForReturnPayloadGraph(id);
if (restored.add(id)) {
/*
* If we haven't seen the id before, use the data in the OperationMessage
* to repopulate the properties of the canonical bean for this id.
*/
OperationMessage op = getOperation(getRequestFactory().getHistoryToken(id));
this.processReturnOperation(id, op);
toReturn.setTag(Constants.STABLE_ID, super.getId(op));
}
return toReturn;
}
/**
* Reset all temporary state.
*/
private void clear() {
syntheticIds.clear();
restored.clear();
serialized.clear();
}
private <T extends BaseProxy> T doDeserialize(SimpleProxyId<T> id) {
try {
return getProxyForReturnPayloadGraph(id).as();
} catch (NoDataException e) {
return null;
} finally {
clear();
}
}
/**
* Load the OperationMessage containing the object state from the backing
* store.
*/
private OperationMessage getOperation(String key) {
Splittable data = store.get(key);
if (data == null) {
throw new NoDataException();
}
OperationMessage op =
AutoBeanCodex.decode(MessageFactoryHolder.FACTORY, OperationMessage.class, data).as();
return op;
}
/**
* Convert any non-persistent ids into store-local synthetic ids.
*/
private <T extends BaseProxy> SimpleProxyId<T> serializedId(SimpleProxyId<T> stableId) {
assert !stableId.isSynthetic();
if (stableId.isEphemeral()) {
@SuppressWarnings("unchecked")
SimpleProxyId<T> syntheticId = (SimpleProxyId<T>) syntheticIds.get(stableId);
if (syntheticId == null) {
int nextId = store.nextId();
assert nextId >= 0 : "ProxyStore.nextId() returned a negative number " + nextId;
syntheticId = getRequestFactory().allocateSyntheticId(stableId.getProxyClass(), nextId + 1);
syntheticIds.put(stableId, syntheticId);
}
return syntheticId;
}
return stableId;
}
private void serializeOneProxy(SimpleProxyId<?> idForSerialization,
AutoBean<? extends BaseProxy> bean) {
AutoBean<OperationMessage> op =
makeOperationMessage(serializedId(BaseProxyCategory.stableId(bean)), bean, false);
store.put(getRequestFactory().getHistoryToken(idForSerialization), AutoBeanCodex.encode(op));
}
}