001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.feedback;
018
019import java.io.Serializable;
020import java.util.ArrayList;
021import java.util.List;
022import java.util.Optional;
023
024import org.apache.wicket.Component;
025import org.apache.wicket.MetaDataKey;
026import org.apache.wicket.Page;
027import org.apache.wicket.WicketRuntimeException;
028import org.apache.wicket.request.cycle.RequestCycle;
029
030/**
031 * Postpone calling {@link IFeedback#beforeRender()} after other components.
032 * <p>
033 * This gives other {@link Component#beforeRender()} the possibility to report feedbacks,
034 * which can then be collected by {@link IFeedback}s afterwards.
035 */
036public class FeedbackDelay implements Serializable, AutoCloseable
037{
038        private static final MetaDataKey<FeedbackDelay> KEY = new MetaDataKey<>()
039        {
040                private static final long serialVersionUID = 1L;
041        };
042        
043        private List<IFeedback> feedbacks = new ArrayList<>();
044
045        private RequestCycle cycle;
046        
047        /**
048         * Delay all feedbacks for the given cycle.
049         * <p>
050         * All postponed feedbacks will be prepared for render with {@link #beforeRender()}.
051         * 
052         * @param cycle
053         *            request cycle
054         */
055        public FeedbackDelay(RequestCycle cycle) {
056                if (get(cycle).isPresent()) {
057                        throw new WicketRuntimeException("feedbacks are already delayed");
058                }
059                
060                cycle.setMetaData(KEY, this);
061                
062                this.cycle = cycle;
063        }
064        
065        /**
066         * Get the current delay.
067         * 
068         * @param cycle
069         * @return optional delay
070         */
071        public static Optional<FeedbackDelay> get(RequestCycle cycle) {
072                return Optional.ofNullable(cycle.getMetaData(KEY));
073        }
074
075        /**
076         * Postpone {@link Component#beforeRender()} on the given feedback.
077         * 
078         * @param feedback
079         * @return
080         */
081        public FeedbackDelay postpone(IFeedback feedback) {
082                feedbacks.add(feedback);
083                
084                return this;
085        }
086
087        /**
088         * Prepares all postponed feedbacks for render.
089         * 
090         * @see IFeedback#beforeRender()
091         */
092        public void beforeRender() {
093                cycle.setMetaData(KEY, null);
094                cycle = null;
095                
096                for (IFeedback feedback : feedbacks)
097                {
098                        if (feedback instanceof Component) {
099                                Component component = (Component)feedback;
100                                
101                                // render only if it is still in the page hierarchy (WICKET-4895)
102                                if (component.findParent(Page.class) == null)
103                                {
104                                        continue;
105                                }                       
106                        }
107                
108                        feedback.beforeRender();
109                }
110        }
111        
112        /**
113         * Close any delays.
114         * <p>
115         * This does not call {@link #beforeRender()} on the delayed feedbacks.
116         */
117        @Override
118        public void close() {
119                if (cycle != null) {
120                        cycle.setMetaData(KEY, null);
121                        cycle = null;
122                        feedbacks.clear();
123                }
124        }
125}