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