/* * � Copyright IBM Corp. 2010 * * 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.ibm.xsp.extlib.model; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.util.Map; import javax.faces.context.FacesContext; import javax.faces.el.ValueBinding; import javax.faces.model.DataModel; import com.ibm.commons.util.StringUtil; import com.ibm.xsp.FacesExceptionEx; import com.ibm.xsp.context.FacesContextEx; import com.ibm.xsp.event.FacesContextListener; import com.ibm.xsp.model.AbstractDataContainer; import com.ibm.xsp.model.AbstractDataSource; import com.ibm.xsp.model.DataContainer; import com.ibm.xsp.model.ModelDataSource; /** * Generic data source that handle data coming from a data accessor. * @author Philippe Riand */ public abstract class DataAccessorSource extends AbstractDataSource implements ModelDataSource { public static class Container extends AbstractDataContainer { private DataAccessor dataAccessor; private boolean clearOnRendering; // FacesContextListener to discard the domino resources at the end of the request private transient FacesContextListener _contextListener; private int generationId; public Container() { // Serialization ctor } public Container(String beanId, String id, DataAccessor dataAccessor, boolean clearOnRendering) { super(beanId,id); this.dataAccessor = dataAccessor; this.clearOnRendering = clearOnRendering; } public int getGenerationId() { return generationId; } public DataAccessorSource getDataSource() { return dataAccessor.getDataSource(); } public void setDataSource(DataAccessorSource dataSource) { this.dataAccessor.setDataSource(dataSource); } public DataAccessor getDataAccessor() { return dataAccessor; } public void serialize(ObjectOutput out) throws IOException { out.writeObject(dataAccessor); out.writeInt(generationId); out.writeBoolean(clearOnRendering); } public void deserialize(ObjectInput in) throws IOException { try { dataAccessor = (DataAccessor)in.readObject(); generationId = in.readInt(); clearOnRendering = in.readBoolean(); } catch (ClassNotFoundException xe) { IOException ioe = new IOException("Error while deserializing object"); // $NLX-DataAccessorSource.Errorwhiledeserializingobject-1$ ioe.initCause(xe); throw ioe; } } public void installFacesListener() { if(_contextListener==null) { FacesContextEx context = FacesContextEx.getCurrentInstance(); _contextListener = new FacesContextListener() { public void beforeContextReleased(FacesContext facesContext) { _contextListener = null; } public void beforeRenderingPhase(FacesContext facesContext) { generationId = generationIdCounter++; // If, for any reason, the view had been read during the previous phases // then we have to refresh it. This prevents some "Entry in index not found" // when document are added/deleted during the POST phases // This flag is set by readEntries when the data are read if(clearOnRendering) { // Keep the count cache dataAccessor.clearData(false); } else { dataAccessor.updateCount(); } } }; // In case of a POST message with a partial execution ID on another control, then // the context is not registered *before* the phase starts if(context.isRenderingPhase()) { generationId = generationIdCounter++; if(clearOnRendering) { // Keep the count cache dataAccessor.clearData(false); } else { dataAccessor.updateCount(); } } context.addRequestListener(_contextListener); } } } private static int generationIdCounter; private Boolean clearOnRendering; private String cacheSuffix; protected DataAccessorSource() { } public boolean isClearOnRendering() { if (null != this.clearOnRendering) { return this.clearOnRendering; } ValueBinding vb = getValueBinding("clearOnRendering"); //$NON-NLS-1$ if (vb != null) { Boolean val = (Boolean) vb.getValue(getFacesContext()); if(val!=null) { return val; } } return false; } public void setClearOnRendering(boolean clearOnRendering) { this.clearOnRendering = clearOnRendering; } public String getCacheSuffix() { if (null != cacheSuffix) { return cacheSuffix; } ValueBinding valueBinding = getValueBinding("cacheSuffix"); //$NON-NLS-1$ if (valueBinding != null) { String value = (String)valueBinding.getValue(getFacesContext()); return value; } return null; } public void setCacheSuffix(String cacheSuffix) { this.cacheSuffix = cacheSuffix; } @Override public Object saveState(FacesContext context) { if (isTransient()) { return null; } Object[] state = new Object[3]; state[0] = super.saveState(context); state[1] = clearOnRendering; state[2] = cacheSuffix; return state; } @Override public void restoreState(FacesContext context, Object state) { Object[] values = (Object[])state; super.restoreState(context, values[0]); clearOnRendering = (Boolean)values[1]; cacheSuffix = (String)values[2]; } /** * This looks like a bug in XPages core where the data container is instantly recreated. * Then, when refresh() is called from the action phase, then the DataContainer is instantly * read with the wrong ids, as it is not yet refreshed because the page is rendered again. * The fix is done in this data source to be sure that nothing breaks in XPages core. */ @Override public void refresh() { FacesContext context = getFacesContext(); if (context == null) return; // clear the current value putDataContainer(context, null); } protected abstract DataAccessor createAccessor(); // @Override // protected String composeUniqueId() { // // This is the best we can do here // return getClass().getName(); // } @Override protected String composeUniqueId() { // For components containing multiple datasources (view, panel...), we distinguish // the data sources by adding the position to the unique id // For other components containing a single data source (view panel..) then we // dont't care about this. if(isDataShared()) { StringBuilder b = new StringBuilder(); b.append(getClass().getName()); String suffix = getCacheSuffix(); if(StringUtil.isNotEmpty(suffix)) { b.append('_'); b.append(suffix); } else { appendDefaultSharedCacheSuffix(b); } return b.toString(); } else { // This is the best we can do here return getClass().getName(); } } protected void appendDefaultSharedCacheSuffix(StringBuilder b) { //throw new FacesExceptionEx(null,"A cache suffix must be provided for a data source of type {0} using scope {1}",getClass().getName(),scope); } @Override public DataAccessor getDataObject() { Container ac = (Container)getDataContainer(); if(ac!=null) { return ac.getDataAccessor(); } return null; } public DataModel getDataModel() { return new DataAccessorModel(this,(Container)getDataContainer()); } @Override public void readRequestParams(FacesContext context,Map<String, Object> requestMap) { } @Override public DataContainer load(FacesContext context) throws IOException { DataAccessor ac = createAccessor(); return new Container(getBeanId(), getUniqueId(),ac, isClearOnRendering()); } @Override public Container getDataContainer(FacesContext context) { Container c = (Container)super.getDataContainer(context); if(c!=null) { c.setDataSource(this); } return c; } @Override public boolean isReadonly() { return true; } @Override public boolean save(FacesContext context, DataContainer data) throws FacesExceptionEx { return false; } }