/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with this * work for additional information regarding copyright ownership. The ASF * licenses this file to you 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. * * $Id: StampState.java 24407 2007-08-30 18:42:15Z atchertchian $ */ package org.nuxeo.ecm.platform.ui.web.component.list; import java.io.Externalizable; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInput; import java.io.ObjectOutput; import java.io.Serializable; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import javax.faces.component.EditableValueHolder; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jboss.seam.ui.component.UIFileUpload; import org.nuxeo.ecm.platform.ui.web.directory.ChainSelect; /** * This class saves the state of stamp components. * <p> * This is an adaptation of the Trinidad component to make it deal correctly * with any kind of component. * * @author <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a> */ final class StampState implements Externalizable { private static final long serialVersionUID = -4207557910028866684L; private static final Log log = LogFactory.getLog(StampState.class); private static final Object[] _EMPTY_ARRAY = new Object[0]; private Map<DualKey, Object> rows; private static final class DualKey implements Serializable { private static final long serialVersionUID = 8302554393951287224L; private final Object key1; private final Object key2; private final int hash; DualKey(Object key1, Object key2) { this.key1 = key1; this.key2 = key2; hash = (key1 == null ? 0 : key1.hashCode()) + (key2 == null ? 0 : key2.hashCode()); } @Override public boolean equals(Object other) { if (other == this) { return true; } if (other instanceof DualKey) { DualKey otherKey = (DualKey) other; if (hashCode() != otherKey.hashCode()) { return false; } return _eq(key1, otherKey.key1) && _eq(key2, otherKey.key2); } return false; } @Override public int hashCode() { return hash; } @Override public String toString() { return "<" + key1 + ',' + key2 + '>'; } } public StampState() { rows = Collections.emptyMap(); } /** * Clears all state except for the state associated with the give * currencyObj. * * @param skipCurrencyObj */ public void clear(Object skipCurrencyObj) { if (!rows.isEmpty()) { Iterator<DualKey> iter = rows.keySet().iterator(); while (iter.hasNext()) { DualKey dk = iter.next(); if (_eq(dk.key1, skipCurrencyObj)) { continue; } iter.remove(); } } } public void put(Object currencyObj, String key, Object value) { Map<DualKey, Object> comparant = Collections.emptyMap(); if (rows == comparant) { // =-=AEW Better default sizes rows = new HashMap<DualKey, Object>(109); } DualKey dk = new DualKey(currencyObj, key); rows.put(dk, value); } public int size() { return rows.size(); } public Object get(Object currencyObj, String key) { DualKey dk = new DualKey(currencyObj, key); return rows.get(dk); } /** * Saves the state of a stamp. This method is called when the currency of * this component is changed so that the state of this stamp can be * preserved, before the stamp is updated with the state corresponding to * the new currency. This method recurses for the children and facets of the * stamp. * * @return this object must be Serializable if client-side state saving is * used. */ public static Object saveStampState(FacesContext context, UIComponent stamp) { if (stamp.isTransient()) { return null; } // because base components use shallow copies in their saveState method, // need to copy separately properties that are likely to change as well // as submitted value that is not saved in UIInput state Object[] selfState = new Object[5]; // XXX AT: NXP-1508: saving the whole state is overkill, but it must be // done for some components that save some info in other fields than // standard EditableValueHolder fields. Object[] innerSelfState = new Object[5]; if (stamp instanceof ChainSelect) { innerSelfState[0] = stamp.saveState(context); } else if (stamp instanceof UIFileUpload) { innerSelfState[0] = stamp.saveState(context); UIFileUpload fileUpload = (UIFileUpload) stamp; innerSelfState[1] = fileUpload.getLocalContentType(); innerSelfState[2] = fileUpload.getLocalFileName(); innerSelfState[3] = fileUpload.getLocalFileSize(); innerSelfState[4] = fileUpload.getLocalInputStream(); } else if (stamp instanceof UIEditableList) { // TODO: don't save the whole state, it may be costly innerSelfState[0] = stamp.saveState(context); } selfState[0] = innerSelfState; // editable value holder standard values saving if (stamp instanceof EditableValueHolder) { EditableValueHolder evh = (EditableValueHolder) stamp; selfState[1] = evh.getSubmittedValue(); selfState[2] = evh.getLocalValue(); selfState[3] = evh.isLocalValueSet(); selfState[4] = evh.isValid(); } Object[] state = new Object[3]; state[0] = selfState; int facetCount = stamp.getFacets().size(); Object[] facetState; if (facetCount == 0) { facetState = _EMPTY_ARRAY; } else { facetState = new Object[facetCount * 2]; Map<String, UIComponent> facetMap = stamp.getFacets(); int i = 0; for (Map.Entry<String, UIComponent> entry : facetMap.entrySet()) { int base = i * 2; UIComponent facet = entry.getValue(); if (!facet.isTransient()) { facetState[base] = entry.getKey(); facetState[base + 1] = saveStampState(context, entry.getValue()); i++; } } } state[1] = facetState; int childCount = stamp.getChildCount(); Object[] childStateArray; if (childCount == 0) { childStateArray = _EMPTY_ARRAY; } else { childStateArray = new Object[childCount]; boolean wasAllTransient = true; int i = 0; for (UIComponent child : stamp.getChildren()) { if (!child.isTransient()) { wasAllTransient = false; childStateArray[i] = saveStampState(context, child); i++; } } // If all we found were transient components, just use // an empty array if (wasAllTransient) { childStateArray = _EMPTY_ARRAY; } } state[2] = childStateArray; return state; } /** * Restores the state of a stamp. This method is called after the currency * of this component is changed so that the state of this stamp can be * changed to match the new currency. This method recurses for the children * and facets of the stamp. */ public static void restoreStampState(FacesContext context, UIComponent stamp, Object stampState) { if (stampState == null || stamp == null) { return; } Object[] state = (Object[]) stampState; Object[] selfState = (Object[]) state[0]; // NXP-1508: restoring specific values for components that do not follow // EditableValueHolder standard interface. Object[] innerSelfState = (Object[]) selfState[0]; if (stamp instanceof ChainSelect) { stamp.restoreState(context, innerSelfState[0]); } else if (stamp instanceof UIFileUpload) { stamp.restoreState(context, innerSelfState[0]); UIFileUpload fileUpload = (UIFileUpload) stamp; fileUpload.setLocalContentType((String) innerSelfState[1]); fileUpload.setLocalFileName((String) innerSelfState[2]); fileUpload.setLocalFileSize((Integer) innerSelfState[3]); fileUpload.setLocalInputStream((InputStream) innerSelfState[4]); } else if (stamp instanceof UIEditableList) { stamp.restoreState(context, innerSelfState[0]); } // editable value holder standard values saving if (stamp instanceof EditableValueHolder) { EditableValueHolder evh = (EditableValueHolder) stamp; evh.setSubmittedValue(selfState[1]); // XXX AT: must set value before localValueSet because UIInput // setValue method will set localValueSet to true. evh.setValue(selfState[2]); if (selfState[3] != null) { evh.setLocalValueSet((Boolean) selfState[3]); } if (selfState[4] != null) { evh.setValid((Boolean) selfState[4]); } } // Force the ID to be reset to reset the client identifier (needed // for UIComponentBase implementation which caches clientId too // aggressively) stamp.setId(stamp.getId()); Object[] facetStateArray = (Object[]) state[1]; for (int i = 0; i < facetStateArray.length; i += 2) { String facetName = (String) facetStateArray[i]; Object facetState = facetStateArray[i + 1]; restoreStampState(context, stamp.getFacet(facetName), facetState); } Object[] childStateArray = (Object[]) state[2]; int childArrayCount = childStateArray.length; int i = 0; for (UIComponent child : stamp.getChildren()) { if (!child.isTransient() && i < childArrayCount) { restoreStampState(context, child, childStateArray[i]); i++; } } } // TODO can do better... public void writeExternal(ObjectOutput out) throws IOException { out.writeInt(rows.size()); if (rows.isEmpty()) { return; } Map<DualKey, Object> map = new HashMap<DualKey, Object>(rows.size()); map.putAll(rows); if (log.isDebugEnabled()) { for (Map.Entry<DualKey, Object> entry : map.entrySet()) { log.debug("Saving " + entry.getKey() + ", " + entry.getValue()); } } out.writeObject(map); } @SuppressWarnings("unchecked") public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { int size = in.readInt(); if (size > 0) { rows = (Map<DualKey, Object>) in.readObject(); } if (log.isDebugEnabled()) { for (Map.Entry<DualKey, Object> entry : rows.entrySet()) { log.debug("Restoring " + entry.getKey() + ", " + entry.getValue()); } } } private static boolean _eq(Object k1, Object k2) { if (k1 == null) { return k2 == null; } return k1.equals(k2); } }