/** * 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 org.brixcms.web.model; import org.apache.wicket.model.IDetachable; import org.apache.wicket.model.IModel; import org.apache.wicket.model.PropertyModel; import org.brixcms.BrixNodeModel; import org.brixcms.jcr.wrapper.BrixNode; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Proxy a BrixNode model object, exposing lightweight transactional semantics through the {@link * org.brixcms.web.model.ModelBuffer.Model#apply} method. Properties of a target model are exposed as models themselves * and maintain status regarding whether changes have been made. When the apply method is called, the changes in the * cached models are iterated and flushed. * <p/> * This is typically used by clients that wish to present a cancelable user interface, without maintaining the original * state of target model object. If the user cancels the operation, the buffer is deallocated without ever applying the * changes to the target. * <p/> * After creating a ModelBuffer, use {@link ModelBuffer#forProperty(java.lang.String)} and {@link * ModelBuffer#forNodeProperty(java.lang.String)} to create and return these cached model objects. These can be provided * to user interface objects. * <p/> * {@link org.brixcms.plugin.site.page.admin.EditTab#EditTab(java.lang.String, org.apache.wicket.model.IModel< * org.brixcms.jcr.wrapper.BrixNode>)} provides a solid use case of this class. */ public class ModelBuffer implements IModel<Object> { private Object target; private Map<String, IModel> propertyMap; private List<Model> models = null; public ModelBuffer() { } /** * Constructor requiring a BrixNode to proxy. * * @param target */ public ModelBuffer(Object target) { this.target = target; } public void detach() { if (target != null && target instanceof IModel) { ((IModel<?>) target).detach(); } } public Object getObject() { return target; } public void setObject(Object object) { target = object; } public void apply() { if (models != null) { for (Model model : models) { model.apply(); } } } /** * Buffer a node property for the target object. * * @param propertyName The JCR key name of the underlying property * @return model A model that buffers the requested property */ public <T> IModel<T> forNodeProperty(String propertyName) { return forProperty(propertyName, true); } /** * Backing method for public forProperty API calls. Property buffers are cached by name, returning an existing buffer * if it was previously created. * * @param propertyName The JCR key name of the underlying property * @param isNode Selector for string type or JcrNode type * @return model A model that buffers the requested property */ @SuppressWarnings("unchecked") protected <T> IModel<T> forProperty(String propertyName, boolean isNode) { if (propertyMap != null && propertyMap.containsKey(propertyName)) { return propertyMap.get(propertyName); } IModel model = forModel(new PropertyModel(this, propertyName), isNode); if (propertyMap == null) { propertyMap = new HashMap<String, IModel>(); } propertyMap.put(propertyName, model); return model; } /** * Create a proxy for a node property. In contrast to {@link org.brixcms.web.model.ModelBuffer#forProperty}, this * method always creates the buffered model. * * @param delegate Accessor to the underlying targetObject, typically a PropertyModel * @param isNode if the datatype is a JcrNode * @return */ public IModel forModel(IModel delegate, boolean isNode) { if (models == null) { models = new ArrayList<Model>(); } Model model; if (!isNode) model = new Model(delegate); else model = new NodeModel(delegate); models.add(model); return model; } /** * Buffer a string property for the target object. * * @param propertyName The JCR key name of the underlying property * @return model A model that buffers the requested property */ public <T> IModel<T> forProperty(String propertyName) { return forProperty(propertyName, false); } /** * Internal storage type for non-JcrNode properties */ private static class Model implements IModel { private final IModel delegate; private Object value; private boolean valueSet = false; public Model(IModel delegate) { this.delegate = delegate; } public Object getObject() { if (valueSet) { return value; } else { return delegate.getObject(); } } public void setObject(Object object) { valueSet = true; value = object; } public void detach() { delegate.detach(); if (value instanceof IDetachable) { ((IDetachable) value).detach(); } } protected void apply(IModel delegate, Object value) { delegate.setObject(value); } public void apply() { if (valueSet) { apply(delegate, value); valueSet = false; } } } /** * Internal storage type for JcrNode properties */ private static class NodeModel extends Model { public NodeModel(IModel delegate) { super(delegate); } @Override public Object getObject() { Object value = super.getObject(); if (value instanceof IModel) { return ((IModel) value).getObject(); } else { return value; } } @Override public void setObject(Object object) { if (object instanceof BrixNode) { super.setObject(new BrixNodeModel((BrixNode) object)); } else { super.setObject(object); } } @Override protected void apply(IModel delegate, Object value) { if (value instanceof IModel) { value = ((IModel) value).getObject(); } super.apply(delegate, value); } } }