/*
* 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.
*/
package org.apache.isis.applib.services.wrapper;
import java.util.Collections;
import java.util.List;
import org.apache.isis.applib.annotation.Hidden;
import org.apache.isis.applib.annotation.Programmatic;
import org.apache.isis.applib.events.InteractionEvent;
import org.apache.isis.applib.services.wrapper.listeners.InteractionListener;
/**
* Provides the ability to "wrap" of a domain object such that it can
* be interacted with while enforcing the hide/disable/validate rules implies by
* the Isis programming model.
*
* <p>
* The "wrap" is a CGLib proxy that wraps the underlying domain
* object. The wrapper can then be interacted with as follows:
* <ul>
* <li>a <tt>get</tt> method for properties or collections</li>
* <li>a <tt>set</tt> method for properties</li>
* <li>an <tt>addTo</tt> or <tt>removeFrom</tt> method for collections</li>
* <li>any action</li>
* </ul>
*
* <p>
* Calling any of the above methods may result in a (subclass of)
* {@link InteractionException} if the object disallows it. For example, if a
* property is annotated with {@link Hidden} then a {@link HiddenException} will
* be thrown. Similarly if an action has a <tt>validate</tt> method and the
* supplied arguments are invalid then a {@link InvalidException} will be
* thrown.
*
* <p>
* In addition, the following methods may also be called:
* <ul>
* <li>the <tt>title</tt> method</li>
* <li>any <tt>defaultXxx</tt> or <tt>choicesXxx</tt> method</li>
* </ul>
*
* <p>
* An exception will be thrown if any other methods are thrown.
*
* <p>
* An implementation of this service (<tt>WrapperFactoryDefault</tt>) can be registered by including
* <tt>o.a.i.core:isis-core-wrapper</tt> on the classpath; no further configuration is required.
* </p>
*/
public interface WrapperFactory {
/**
* Whether interactions with the wrapper are actually passed onto the
* underlying domain object.
*
* @see WrapperFactory#wrap(Object, ExecutionMode)
*/
public static enum ExecutionMode {
/**
* Validate all business rules and then execute.
*/
EXECUTE(true,true),
/**
* Skip all business rules and then execute.
*/
SKIP_RULES(false, true),
/**
* Validate all business rules but do not execute.
*/
NO_EXECUTE(true, false);
private final boolean enforceRules;
private final boolean execute;
private ExecutionMode(final boolean enforceRules, final boolean execute) {
this.enforceRules = enforceRules;
this.execute = execute;
}
public boolean shouldEnforceRules() {
return enforceRules;
}
public boolean shouldExecute() {
return execute;
}
}
WrapperFactory NOOP = new WrapperFactory(){
@Override
public <T> T wrap(T domainObject) {
return domainObject;
}
@Override
public <T> T wrapNoExecute(T domainObject) {
return domainObject;
}
@Override
public <T> T wrapSkipRules(T domainObject) {
return domainObject;
}
@Override
public <T> T wrap(T domainObject, ExecutionMode mode) {
return domainObject;
}
@Override
public <T> T unwrap(T possibleWrappedDomainObject) {
return possibleWrappedDomainObject;
}
@Override
public <T> boolean isWrapper(T possibleWrappedDomainObject) {
return false;
}
@Override
public List<InteractionListener> getListeners() {
return Collections.emptyList();
}
@Override
public boolean addInteractionListener(InteractionListener listener) {
return false;
}
@Override
public boolean removeInteractionListener(InteractionListener listener) {
return false;
}
@Override
public void notifyListeners(InteractionEvent ev) {
}
};
/**
* Provides the "wrapper" of the underlying domain object.
*
* <p>
* If the object has (see {@link #isWrapper(Object)} already been wrapped),
* then should just return the object back unchanged.
*/
@Programmatic
<T> T wrap(T domainObject);
/**
* Convenience method for {@link #wrap(Object, ExecutionMode)} with {@link ExecutionMode#NO_EXECUTE},
* to make this feature more discoverable.
*/
@Programmatic
<T> T wrapNoExecute(T domainObject);
/**
* Convenience method for {@link #wrap(Object, ExecutionMode)} with {@link ExecutionMode#SKIP_RULES},
* to make this feature more discoverable.
*/
@Programmatic
<T> T wrapSkipRules(T domainObject);
/**
* Same as {@link #wrap(Object)}, except the actual execution occurs only if
* the <tt>execute</tt> parameter indicates.
*
* <p>
* Otherwise, will do all the validations (raise exceptions as required
* etc.), but doesn't modify the model.
*/
@Programmatic
<T> T wrap(T domainObject, ExecutionMode mode);
/**
* Obtains the underlying domain object, if wrapped.
*
* <p>
* If the object {@link #isWrapper(Object) is not wrapped}, then
* should just return the object back unchanged.
*/
@Programmatic
<T> T unwrap(T possibleWrappedDomainObject);
/**
* Whether the supplied object has been wrapped.
*
* @param <T>
* @param possibleWrappedDomainObject
* - object that might or might not be a wrapper.
* @return
*/
@Programmatic
<T> boolean isWrapper(T possibleWrappedDomainObject);
/**
* All {@link InteractionListener}s that have been registered using
* {@link #addInteractionListener(InteractionListener)}.
*/
@Programmatic
List<InteractionListener> getListeners();
/**
* Registers an {@link InteractionListener}, to be notified of interactions
* on all wrappers.
*
* <p>
* This is retrospective: the listener will be notified of interactions even
* on wrappers created before the listener was installed. (From an
* implementation perspective this is because the wrappers delegate back to
* the container to fire the events).
*
* @param listener
* @return
*/
@Programmatic
public boolean addInteractionListener(InteractionListener listener);
/**
* Remove an {@link InteractionListener}, to no longer be notified of
* interactions on wrappers.
*
* <p>
* This is retrospective: the listener will no longer be notified of any
* interactions created on any wrappers, not just on those wrappers created
* subsequently. (From an implementation perspective this is because the
* wrappers delegate back to the container to fire the events).
*
* @param listener
* @return
*/
@Programmatic
public boolean removeInteractionListener(InteractionListener listener);
@Programmatic
public void notifyListeners(InteractionEvent ev);
}