/*
* 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.viewer.wicket.ui.components.widgets.linkandlabel;
import org.apache.wicket.Application;
import org.apache.wicket.RestartResponseException;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.IAjaxIndicatorAware;
import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.extensions.ajax.markup.html.AjaxIndicatorAppender;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.request.IRequestHandler;
import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
import org.apache.isis.core.metamodel.adapter.version.ConcurrencyException;
import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
import org.apache.isis.core.runtime.system.context.IsisContext;
import org.apache.isis.viewer.wicket.model.isis.WicketViewerSettings;
import org.apache.isis.viewer.wicket.model.isis.WicketViewerSettingsAccessor;
import org.apache.isis.viewer.wicket.model.models.ActionModel;
import org.apache.isis.viewer.wicket.ui.pages.entity.EntityPage;
import org.apache.isis.viewer.wicket.ui.panels.PanelUtil;
import de.agilecoders.wicket.core.markup.html.bootstrap.button.Buttons;
public abstract class ActionLink extends AjaxLink<ObjectAdapter> implements IAjaxIndicatorAware {
private static final long serialVersionUID = 1L;
private final AjaxIndicatorAppender indicatorAppenderIfAny;
final AjaxDeferredBehaviour ajaxDeferredBehaviourIfAny;
public ActionLink(String id, ActionModel model) {
this(id, model, null);
}
ActionLink(String id, ActionModel model, ObjectAction action) {
super(id, model);
final boolean useIndicatorForNoArgAction = getSettings().isUseIndicatorForNoArgAction();
this.indicatorAppenderIfAny =
useIndicatorForNoArgAction
? new AjaxIndicatorAppender()
: null;
if(this.indicatorAppenderIfAny != null) {
this.add(this.indicatorAppenderIfAny);
}
// trivial optimization; also store the objectAction if it is available (saves looking it up)
objectAction = action;
// this returns non-null if the action is no-arg and returns a URL or a Blob or a Clob.
// Otherwise can use default handling
// TODO: the method looks at the actual compile-time return type;
// TODO: cannot see a way to check at runtime what is returned.
// TODO: see https://issues.apache.org/jira/browse/ISIS-1264 for further detail.
ajaxDeferredBehaviourIfAny = determineDeferredBehaviour();
if(ajaxDeferredBehaviourIfAny != null) {
this.add(ajaxDeferredBehaviourIfAny);
}
}
@Override
public void onClick(AjaxRequestTarget target) {
if (ajaxDeferredBehaviourIfAny != null) {
ajaxDeferredBehaviourIfAny.initiate(target);
return;
}
doOnClick(target);
}
protected abstract void doOnClick(AjaxRequestTarget target);
ActionModel getActionModel() {
return (ActionModel) getModel();
}
private transient ObjectAction objectAction;
public ObjectAction getObjectAction() {
return objectAction != null
? objectAction
: (objectAction = getActionModel().getActionMemento().getAction(getSpecificationLoader()));
}
@Override
protected void updateAjaxAttributes(AjaxRequestAttributes attributes) {
super.updateAjaxAttributes(attributes);
if(getSettings().isPreventDoubleClickForNoArgAction()) {
PanelUtil.disableBeforeReenableOnComplete(attributes, this);
}
// allow the event to bubble so the menu is hidden after click on an item
attributes.setEventPropagation(AjaxRequestAttributes.EventPropagation.BUBBLE);
}
public String getReasonDisabledIfAny() {
return getActionModel().getReasonDisabledIfAny();
}
@Override
public boolean isVisible() {
return getActionModel().isVisible();
}
@Override
public boolean isEnabled() {
try {
final String reasonDisabledIfAny = getReasonDisabledIfAny();
return reasonDisabledIfAny == null;
} catch (ConcurrencyException ex) {
//
// this has to be here because it's the first method called by an action link listener
// on a potentially stale model.
//
// there is similar code for editing properties (ScalarPanelAbstract2)
//
IsisContext.getSessionFactory().getCurrentSession().getAuthenticationSession().getMessageBroker().addMessage(ex.getMessage());
throw new RestartResponseException(new EntityPage(getActionModel().getTargetAdapter()));
}
}
@Override
protected void onComponentTag(ComponentTag tag) {
super.onComponentTag(tag);
Buttons.fixDisabledState(this, tag);
}
public String getAjaxIndicatorMarkupId() {
return this.indicatorAppenderIfAny != null
? this.indicatorAppenderIfAny.getMarkupId()
: null;
}
protected WicketViewerSettings getSettings() {
return ((WicketViewerSettingsAccessor) Application.get()).getSettings();
}
protected SpecificationLoader getSpecificationLoader() {
return IsisContext.getSessionFactory().getSpecificationLoader();
}
AjaxDeferredBehaviour determineDeferredBehaviour() {
final ObjectAction action = getObjectAction();
final ActionModel actionModel = this.getActionModel();
// TODO: should unify with ActionResultResponseType (as used in ActionParametersPanel)
if (isNoArgReturnTypeRedirect(action)) {
/**
* adapted from:
*
* @see https://cwiki.apache.org/confluence/display/WICKET/AJAX+update+and+file+download+in+one+blow
*/
return new AjaxDeferredBehaviour(AjaxDeferredBehaviour.OpenUrlStrategy.NEW_WINDOW) {
private static final long serialVersionUID = 1L;
@Override
protected IRequestHandler getRequestHandler() {
ObjectAdapter resultAdapter = actionModel.execute();
final Object value = resultAdapter.getObject();
return ActionModel.redirectHandler(value);
}
};
}
if (isNoArgReturnTypeDownload(action)) {
/**
* adapted from:
*
* @see https://cwiki.apache.org/confluence/display/WICKET/AJAX+update+and+file+download+in+one+blow
*/
return new AjaxDeferredBehaviour(AjaxDeferredBehaviour.OpenUrlStrategy.SAME_WINDOW) {
private static final long serialVersionUID = 1L;
@Override
protected IRequestHandler getRequestHandler() {
final ObjectAdapter resultAdapter = actionModel.execute();
final Object value = resultAdapter.getObject();
return ActionModel.downloadHandler(value);
}
};
}
return null;
}
// TODO: should unify with ActionResultResponseType (as used in ActionParametersPanel)
private static boolean isNoArgReturnTypeRedirect(final ObjectAction action) {
return action.getParameterCount() == 0 &&
action.getReturnType() != null &&
action.getReturnType().getCorrespondingClass() == java.net.URL.class;
}
// TODO: should unify with ActionResultResponseType (as used in ActionParametersPanel)
private static boolean isNoArgReturnTypeDownload(final ObjectAction action) {
return action.getParameterCount() == 0 && action.getReturnType() != null &&
(action.getReturnType().getCorrespondingClass() == org.apache.isis.applib.value.Blob.class ||
action.getReturnType().getCorrespondingClass() == org.apache.isis.applib.value.Clob.class);
}
}