/* * 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.server; import com.google.web.bindery.autobean.shared.AutoBean; import com.google.web.bindery.autobean.shared.AutoBeanCodex; import com.google.web.bindery.autobean.shared.Splittable; import com.google.web.bindery.autobean.shared.ValueCodex; import com.google.web.bindery.autobean.shared.impl.StringQuoter; import com.google.web.bindery.autobean.vm.AutoBeanFactorySource; import com.google.web.bindery.requestfactory.server.SimpleRequestProcessor.IdToEntityMap; 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 com.google.web.bindery.requestfactory.shared.impl.Constants; import com.google.web.bindery.requestfactory.shared.impl.EntityCodex; import com.google.web.bindery.requestfactory.shared.impl.IdFactory; import com.google.web.bindery.requestfactory.shared.impl.MessageFactoryHolder; import com.google.web.bindery.requestfactory.shared.impl.SimpleProxyId; import com.google.web.bindery.requestfactory.shared.messages.IdMessage; import com.google.web.bindery.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 = AutoBeanFactorySource.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; } }