blob: c93d89310692105f2caa9ca712abeb1046aef931 [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.requestfactory.shared.BaseProxy;
import com.google.web.bindery.requestfactory.shared.EntityProxy;
import com.google.web.bindery.requestfactory.shared.ValueProxy;
import java.util.HashMap;
import java.util.Map;
/**
* Handles common code for creating SimpleProxyIds.
*/
public abstract class IdFactory {
/**
* Maps ephemeral history tokens to an id object. This canonicalizing mapping
* resolves the problem of EntityProxyIds hashcodes changing after persist.
* Only ids that are created in the RequestFactory are stored here.
*/
private final Map<String, SimpleProxyId<?>> ephemeralIds = new HashMap<String, SimpleProxyId<?>>();
/**
* Allocates an ephemeral proxy id. This object is only valid for the lifetime
* of the RequestFactory.
*/
public <P extends BaseProxy> SimpleProxyId<P> allocateId(Class<P> clazz) {
SimpleProxyId<P> toReturn = createId(clazz, ephemeralIds.size() + 1);
ephemeralIds.put(getHistoryToken(toReturn), toReturn);
return toReturn;
}
/**
* Allocates a synthetic proxy id. This object is only valid for the lifetime
* of a request.
*/
public <P extends BaseProxy> SimpleProxyId<P> allocateSyntheticId(
Class<P> clazz, int syntheticId) {
assert syntheticId > 0;
SimpleProxyId<P> toReturn = createId(clazz, "%" + syntheticId);
toReturn.setSyntheticId(syntheticId);
return toReturn;
}
/**
* A utility function to handle generic type conversion. This method will also
* assert that {@code clazz} is actually an EntityProxy type.
*/
@SuppressWarnings("unchecked")
public <P extends EntityProxy> Class<P> asEntityProxy(
Class<? extends BaseProxy> clazz) {
assert isEntityType(clazz) : clazz.getName()
+ " is not an EntityProxy type";
return (Class<P>) clazz;
}
/**
* A utility function to handle generic type conversion. This method will also
* assert that {@code clazz} is actually a ValueProxy type.
*/
@SuppressWarnings("unchecked")
public <P extends ValueProxy> Class<P> asValueProxy(
Class<? extends BaseProxy> clazz) {
assert isValueType(clazz) : clazz.getName() + " is not a ValueProxy type";
return (Class<P>) clazz;
}
public <P extends BaseProxy> SimpleProxyId<P> getBaseProxyId(
String historyToken) {
assert !IdUtil.isSynthetic(historyToken) : "Synthetic id resolution"
+ " should be handled by AbstractRequestContext";
if (IdUtil.isPersisted(historyToken)) {
return getId(IdUtil.getTypeToken(historyToken),
IdUtil.getServerId(historyToken));
}
if (IdUtil.isEphemeral(historyToken)) {
@SuppressWarnings("unchecked")
SimpleProxyId<P> toReturn = (SimpleProxyId<P>) ephemeralIds.get(historyToken);
/*
* This is tested in FindServiceTest.testFetchUnpersistedFutureId. In
* order to get here, the user would have to get an unpersisted history
* token and attempt to use it with a different RequestFactory instance.
* This could occur if an ephemeral token were bookmarked. In this case,
* we'll create a token, however it will never match anything.
*/
if (toReturn == null) {
Class<P> clazz = checkTypeToken(IdUtil.getTypeToken(historyToken));
toReturn = createId(clazz, -1 * ephemeralIds.size());
ephemeralIds.put(historyToken, toReturn);
}
return toReturn;
}
throw new IllegalArgumentException(historyToken);
}
public String getHistoryToken(SimpleProxyId<?> proxy) {
SimpleProxyId<?> id = (SimpleProxyId<?>) proxy;
String token = getTypeToken(proxy.getProxyClass());
if (id.isEphemeral()) {
return IdUtil.ephemeralId(id.getClientId(), token);
} else if (id.isSynthetic()) {
return IdUtil.syntheticId(id.getSyntheticId(), token);
} else {
return IdUtil.persistedId(id.getServerId(), token);
}
}
/**
* Create or retrieve a SimpleProxyId. If both the serverId and clientId are
* specified and the id is ephemeral, it will be updated with the server id.
*/
public <P extends BaseProxy> SimpleProxyId<P> getId(Class<P> clazz,
String serverId, int clientId) {
return getId(getTypeToken(clazz), serverId, clientId);
}
/**
* Create or retrieve a SimpleProxyId.
*/
public <P extends BaseProxy> SimpleProxyId<P> getId(String typeToken,
String serverId) {
return getId(typeToken, serverId, 0);
}
/**
* Create or retrieve a SimpleEntityProxyId. If both the serverId and clientId
* are specified and the id is ephemeral, it will be updated with the server
* id.
*/
public <P extends BaseProxy> SimpleProxyId<P> getId(String typeToken,
String serverId, int clientId) {
/*
* If there's a clientId, that probably means we've just created a brand-new
* EntityProxy or have just persisted something on the server.
*/
if (clientId > 0) {
// Try a cache lookup for the ephemeral key
String ephemeralKey = IdUtil.ephemeralId(clientId, typeToken);
@SuppressWarnings("unchecked")
SimpleProxyId<P> toReturn = (SimpleProxyId<P>) ephemeralIds.get(ephemeralKey);
// Do we need to allocate an ephemeral id?
if (toReturn == null) {
Class<P> clazz = getTypeFromToken(typeToken);
toReturn = createId(clazz, clientId);
ephemeralIds.put(ephemeralKey, toReturn);
}
// If it's ephemeral, see if we have a serverId and save it
if (toReturn.isEphemeral()) {
// Sanity check
assert toReturn.getProxyClass().equals(getTypeFromToken(typeToken));
if (serverId != null) {
/*
* Record the server id so a later "find" operation will have an equal
* stableId.
*/
toReturn.setServerId(serverId);
String serverKey = IdUtil.persistedId(serverId, typeToken);
ephemeralIds.put(serverKey, toReturn);
}
}
return toReturn;
}
// Should never get this far without a server id
assert serverId != null : "serverId";
String serverKey = IdUtil.persistedId(serverId, typeToken);
@SuppressWarnings("unchecked")
SimpleProxyId<P> toReturn = (SimpleProxyId<P>) ephemeralIds.get(serverKey);
if (toReturn != null) {
// A cache hit for a locally-created object that has been persisted
return toReturn;
}
/*
* No existing id, so it was never an ephemeral id created by this
* RequestFactory, so we don't need to record it. This should be the normal
* case for read-dominated applications.
*/
Class<P> clazz = getTypeFromToken(typeToken);
assert clazz != null : "No class literal for " + typeToken;
return createId(clazz, serverId);
}
public abstract boolean isEntityType(Class<?> clazz);
public abstract boolean isValueType(Class<?> clazz);
protected abstract <P extends BaseProxy> Class<P> getTypeFromToken(
String typeToken);
protected abstract String getTypeToken(Class<? extends BaseProxy> clazz);
private <P> Class<P> checkTypeToken(String token) {
@SuppressWarnings("unchecked")
Class<P> clazz = (Class<P>) getTypeFromToken(token);
if (clazz == null) {
throw new IllegalArgumentException("Unknnown type");
}
return clazz;
}
private <P extends BaseProxy> SimpleProxyId<P> createId(Class<P> clazz,
int clientId) {
SimpleProxyId<P> toReturn;
if (isValueType(clazz)) {
toReturn = new SimpleProxyId<P>(clazz, clientId);
} else {
@SuppressWarnings("unchecked")
SimpleProxyId<P> temp = (SimpleProxyId<P>) new SimpleEntityProxyId<EntityProxy>(
asEntityProxy(clazz), clientId);
toReturn = (SimpleProxyId<P>) temp;
}
return toReturn;
}
private <P extends BaseProxy> SimpleProxyId<P> createId(Class<P> clazz,
String serverId) {
SimpleProxyId<P> toReturn;
if (isValueType(clazz)) {
toReturn = new SimpleProxyId<P>(clazz, serverId);
} else {
@SuppressWarnings("unchecked")
SimpleProxyId<P> temp = (SimpleProxyId<P>) new SimpleEntityProxyId<EntityProxy>(
asEntityProxy(clazz), serverId);
toReturn = (SimpleProxyId<P>) temp;
}
return toReturn;
}
}