| /* | |
| * 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; | |
| /** | |
| * 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 IdentityHashMap<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 and adds client ids for stable ids | |
| * that were ephemeral. | |
| * <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 { | |
| if (stableId.wasEphemeral()) { | |
| ref.setClientId(stableId.getClientId()); | |
| } | |
| 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; | |
| } | |
| } |