/* * 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; } }