blob: 520e6d75bc067496e30f78a2c70f13bcfb7e513a [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.gwt.requestfactory.server;
import com.google.gwt.autobean.server.AutoBeanFactoryMagic;
import com.google.gwt.autobean.shared.AutoBean;
import com.google.gwt.autobean.shared.AutoBeanCodex;
import com.google.gwt.autobean.shared.Splittable;
import com.google.gwt.autobean.shared.ValueCodex;
import com.google.gwt.autobean.shared.impl.StringQuoter;
import com.google.gwt.requestfactory.server.SimpleRequestProcessor.IdToEntityMap;
import com.google.gwt.requestfactory.shared.BaseProxy;
import com.google.gwt.requestfactory.shared.EntityProxy;
import com.google.gwt.requestfactory.shared.ValueProxy;
import com.google.gwt.requestfactory.shared.impl.Constants;
import com.google.gwt.requestfactory.shared.impl.EntityCodex;
import com.google.gwt.requestfactory.shared.impl.IdFactory;
import com.google.gwt.requestfactory.shared.impl.MessageFactoryHolder;
import com.google.gwt.requestfactory.shared.impl.SimpleProxyId;
import com.google.gwt.requestfactory.shared.messages.IdMessage;
import com.google.gwt.requestfactory.shared.messages.IdMessage.Strength;
import java.util.ArrayList;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* Encapsulates all state relating to the processing of a single request so that
* the SimpleRequestProcessor can be stateless.
*/
class RequestState implements EntityCodex.EntitySource {
final IdToEntityMap beans = new IdToEntityMap();
private final Map<Object, SimpleProxyId<?>> domainObjectsToId;
private final IdFactory idFactory;
private final ServiceLayer service;
private final Resolver resolver;
public RequestState(RequestState parent) {
idFactory = parent.idFactory;
domainObjectsToId = parent.domainObjectsToId;
service = parent.service;
resolver = new Resolver(this);
}
public RequestState(final ServiceLayer service) {
this.service = service;
idFactory = new IdFactory() {
@Override
public boolean isEntityType(Class<?> clazz) {
return EntityProxy.class.isAssignableFrom(clazz);
}
@Override
public boolean isValueType(Class<?> clazz) {
return ValueProxy.class.isAssignableFrom(clazz);
}
@Override
@SuppressWarnings("unchecked")
protected <P extends BaseProxy> Class<P> getTypeFromToken(String typeToken) {
return (Class<P>) service.resolveClass(typeToken);
}
@Override
protected String getTypeToken(Class<? extends BaseProxy> clazz) {
return service.resolveTypeToken(clazz);
}
};
domainObjectsToId = new IdentityHashMap<Object, SimpleProxyId<?>>();
resolver = new Resolver(this);
}
/**
* Turn a domain value into a wire format message.
*/
public Splittable flatten(Object domainValue) {
Splittable flatValue;
if (ValueCodex.canDecode(domainValue.getClass())) {
flatValue = ValueCodex.encode(domainValue);
} else {
flatValue = new SimpleRequestProcessor(service).createOobMessage(Collections.singletonList(domainValue));
}
return flatValue;
}
/**
* Get or create a BaseProxy AutoBean for the given id.
*/
public <Q extends BaseProxy> AutoBean<Q> getBeanForPayload(
SimpleProxyId<Q> id, Object domainObject) {
@SuppressWarnings("unchecked")
AutoBean<Q> toReturn = (AutoBean<Q>) beans.get(id);
if (toReturn == null) {
toReturn = createProxyBean(id, domainObject);
}
return toReturn;
}
/**
* EntityCodex support.
*/
public <Q extends BaseProxy> AutoBean<Q> getBeanForPayload(
Splittable serializedProxyId) {
IdMessage idMessage = AutoBeanCodex.decode(MessageFactoryHolder.FACTORY,
IdMessage.class, serializedProxyId).as();
@SuppressWarnings("unchecked")
AutoBean<Q> toReturn = (AutoBean<Q>) getBeansForPayload(
Collections.singletonList(idMessage)).get(0);
return toReturn;
}
/**
* Get or create BaseProxy AutoBeans for a list of id-bearing messages.
*/
public List<AutoBean<? extends BaseProxy>> getBeansForPayload(
List<? extends IdMessage> idMessages) {
List<SimpleProxyId<?>> ids = new ArrayList<SimpleProxyId<?>>(
idMessages.size());
for (IdMessage idMessage : idMessages) {
SimpleProxyId<?> id;
if (Strength.SYNTHETIC.equals(idMessage.getStrength())) {
Class<? extends BaseProxy> clazz = service.resolveClass(idMessage.getTypeToken());
id = idFactory.allocateSyntheticId(clazz, idMessage.getSyntheticId());
} else {
String decodedId = idMessage.getServerId() == null ? null
: SimpleRequestProcessor.fromBase64(idMessage.getServerId());
id = idFactory.getId(idMessage.getTypeToken(), decodedId,
idMessage.getClientId());
}
ids.add(id);
}
return getBeansForIds(ids);
}
public IdFactory getIdFactory() {
return idFactory;
}
public Resolver getResolver() {
return resolver;
}
/**
* EntityCodex support. This method is identical to
* {@link IdFactory#getHistoryToken(SimpleProxyId)} except that it
* base64-encodes the server ids.
* <p>
* XXX: Merge this with AbstsractRequestContext's implementation
*/
public Splittable getSerializedProxyId(SimpleProxyId<?> stableId) {
AutoBean<IdMessage> bean = MessageFactoryHolder.FACTORY.id();
IdMessage ref = bean.as();
ref.setTypeToken(service.resolveTypeToken(stableId.getProxyClass()));
if (stableId.isSynthetic()) {
ref.setStrength(Strength.SYNTHETIC);
ref.setSyntheticId(stableId.getSyntheticId());
} else if (stableId.isEphemeral()) {
ref.setStrength(Strength.EPHEMERAL);
ref.setClientId(stableId.getClientId());
} else {
ref.setServerId(SimpleRequestProcessor.toBase64(stableId.getServerId()));
}
return AutoBeanCodex.encode(bean);
}
public ServiceLayer getServiceLayer() {
return service;
}
/**
* If the given domain object has been previously associated with an id,
* return it
*/
public SimpleProxyId<?> getStableId(Object domain) {
return domainObjectsToId.get(domain);
}
/**
* EntityCodex support.
*/
public boolean isEntityType(Class<?> clazz) {
return idFactory.isEntityType(clazz);
}
/**
* EntityCodex support.
*/
public boolean isValueType(Class<?> clazz) {
return idFactory.isValueType(clazz);
}
/**
* Creates an AutoBean for the given id, tracking a domain object.
*/
private <Q extends BaseProxy> AutoBean<Q> createProxyBean(
SimpleProxyId<Q> id, Object domainObject) {
AutoBean<Q> toReturn = AutoBeanFactoryMagic.createBean(id.getProxyClass(),
SimpleRequestProcessor.CONFIGURATION);
toReturn.setTag(Constants.STABLE_ID, id);
toReturn.setTag(Constants.DOMAIN_OBJECT, domainObject);
beans.put(id, toReturn);
return toReturn;
}
/**
* Returns the AutoBeans corresponding to the given ids, or creates them if
* they do not yet exist.
*/
private List<AutoBean<? extends BaseProxy>> getBeansForIds(
List<SimpleProxyId<?>> ids) {
List<Class<?>> domainClasses = new ArrayList<Class<?>>(ids.size());
List<Object> domainIds = new ArrayList<Object>(ids.size());
List<SimpleProxyId<?>> idsToLoad = new ArrayList<SimpleProxyId<?>>();
/*
* Create proxies for ephemeral or synthetic ids that we haven't seen. Queue
* up the domain ids for entities that need to be loaded.
*/
for (SimpleProxyId<?> id : ids) {
Class<?> domainClass = service.resolveDomainClass(id.getProxyClass());
if (beans.containsKey(id)) {
// Already have a proxy for this id, no-op
} else if (id.isEphemeral() || id.isSynthetic()) {
// Create a new domain object for the short-lived id
Object domain = service.createDomainObject(domainClass);
if (domain == null) {
throw new UnexpectedException("Could not create instance of "
+ domainClass.getCanonicalName(), null);
}
AutoBean<? extends BaseProxy> bean = createProxyBean(id, domain);
beans.put(id, bean);
domainObjectsToId.put(domain, id);
} else {
// Decode the domain parameter
Splittable split = StringQuoter.split(id.getServerId());
Class<?> param = service.getIdType(domainClass);
Object domainParam;
if (ValueCodex.canDecode(param)) {
domainParam = ValueCodex.decode(param, split);
} else {
domainParam = new SimpleRequestProcessor(service).decodeOobMessage(
param, split).get(0);
}
// Enqueue
domainClasses.add(service.resolveDomainClass(id.getProxyClass()));
domainIds.add(domainParam);
idsToLoad.add(id);
}
}
// Actually load the data
if (!domainClasses.isEmpty()) {
assert domainClasses.size() == domainIds.size()
&& domainClasses.size() == idsToLoad.size();
List<Object> loaded = service.loadDomainObjects(domainClasses, domainIds);
if (idsToLoad.size() != loaded.size()) {
throw new UnexpectedException("Expected " + idsToLoad.size()
+ " objects to be loaded, got " + loaded.size(), null);
}
Iterator<Object> itLoaded = loaded.iterator();
for (SimpleProxyId<?> id : idsToLoad) {
Object domain = itLoaded.next();
domainObjectsToId.put(domain, id);
AutoBean<? extends BaseProxy> bean = createProxyBean(id, domain);
beans.put(id, bean);
}
}
// Construct the return value
List<AutoBean<? extends BaseProxy>> toReturn = new ArrayList<AutoBean<? extends BaseProxy>>(
ids.size());
for (SimpleProxyId<?> id : ids) {
toReturn.add(beans.get(id));
}
return toReturn;
}
}