001/*
002 * (C) Copyright 2006-2018 Nuxeo (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     bstefanescu
018 */
019package org.nuxeo.runtime.test.runner;
020
021import java.security.InvalidParameterException;
022
023import org.nuxeo.runtime.api.Framework;
024import org.nuxeo.runtime.model.ComponentManager;
025
026/**
027 * A dynamic component deployer which enable tests to deploy new contributions after the test was started (i.e. from
028 * inside the test method) The deployer is reloading all the components and reinject the test members.
029 *
030 * @author bogdan
031 */
032public class HotDeployer {
033
034    public static final String DEPLOY_ACTION = "deploy";
035
036    protected static final String UNDEPLOY_ACTION = "undeploy";
037
038    public static final String RESTART_ACTION = "restart";
039
040    public static final String RESET_ACTION = "reset";
041
042    public static final String RELOAD_ACTION = "reload";
043
044    protected FeaturesRunner runner;
045
046    protected RuntimeHarness harness;
047
048    protected ActionHandler head;
049
050    public HotDeployer(FeaturesRunner runner, RuntimeHarness harness) {
051        this.runner = runner;
052        this.harness = harness;
053        head = new DefaultDeployHandler();
054    }
055
056    /**
057     * Add a custom deploy action that wraps the default action. You should call next.exec(...) in your action code to
058     * call the next action
059     */
060    public HotDeployer addHandler(ActionHandler action) {
061        action.next = head;
062        head = action;
063        return this;
064    }
065
066    /**
067     * Remove the given handler if already registered
068     */
069    public boolean removeHandler(ActionHandler handler) {
070        ActionHandler p = null;
071        ActionHandler h = head;
072        while (h != null && h != handler) {
073            p = h;
074            h = h.next;
075        }
076        if (h != null) {
077            if (p == null) {
078                head = h.next;
079            } else {
080                p.next = h.next;
081            }
082            h.next = null;
083            return true;
084        }
085        return false;
086    }
087
088    /**
089     * Deploy the given list of contributions. The format is bundleId[:componentPath]. If no component path is
090     * specified then the bundle identified by the bundleId part will be deployed. If a componentPath is given
091     * {@link RuntimeHarness#deployContrib(String,String)} will be used to deploy the contribution.
092     */
093    public void deploy(String... contribs) throws Exception {
094        head.exec(DEPLOY_ACTION, contribs);
095        reinject();
096    }
097
098    public void undeploy(String... contribs) throws Exception {
099        head.exec(UNDEPLOY_ACTION, contribs);
100        reinject();
101    }
102
103    /**
104     * Restart the components and preserve the current registry state.
105     */
106    public void restart() throws Exception {
107        head.exec(RESTART_ACTION);
108        reinject();
109    }
110
111    /**
112     * Restart the components and revert to the initial registry snapshot if any.
113     */
114    public void reset() throws Exception {
115        head.exec(RESET_ACTION);
116        reinject();
117    }
118
119    /**
120     * Reloads the components and preserve the current registry state. This action performs a standby/resume on
121     * {@link ComponentManager}.
122     *
123     * @since 10.2
124     */
125    public void reload() throws Exception {
126        head.exec(RELOAD_ACTION);
127        reinject();
128    }
129
130    public void reinject() {
131        runner.getInjector().injectMembers(runner.getTargetTestInstance());
132    }
133
134    /**
135     * Deploy actions are usually used by features to customize the deployment of the runtime feature (which is using
136     * the DeaultDeployAction)
137     *
138     * @author bogdan
139     */
140    public static abstract class ActionHandler {
141
142        protected ActionHandler next;
143
144        /**
145         * Can wrap another deploy action with custom code. Should call the next action using
146         * <code>next.exec(action, args...)</code>
147         */
148        public abstract void exec(String action, String... args) throws Exception;
149
150    }
151
152    /**
153     * This action has no next action and will deploy the contributions and then reload the component manager
154     *
155     * @author bogdan
156     */
157    protected class DefaultDeployHandler extends ActionHandler {
158
159        @Override
160        public void exec(String action, String... args) throws Exception {
161            if (DEPLOY_ACTION.equals(action)) {
162                deploy(args);
163            } else if (UNDEPLOY_ACTION.equals(action)) {
164                undeploy(args);
165            } else if (RESTART_ACTION.equals(action)) {
166                restart();
167            } else if (RESET_ACTION.equals(action)) {
168                reset();
169            } else if (RELOAD_ACTION.equals(action)) {
170                reload();
171            }
172        }
173
174        public void undeploy(String... contribs) throws Exception {
175            if (contribs != null && contribs.length > 0) {
176                for (String contrib : contribs) {
177                    int i = contrib.indexOf(':');
178                    if (i == -1) {
179                        throw new InvalidParameterException("Cannot undeploy bundle " + contrib);
180                    }
181                    harness.undeployContrib(contrib.substring(0, i), contrib.substring(i + 1));
182                }
183            }
184            // use false to prevent removing the local test method deployments
185            Framework.getRuntime().getComponentManager().refresh(false);
186        }
187
188        public void deploy(String... contribs) throws Exception {
189            if (contribs != null && contribs.length > 0) {
190                for (String contrib : contribs) {
191                    int i = contrib.indexOf(':');
192                    if (i > -1) {
193                        String bundleId = contrib.substring(0, i);
194                        if (bundleId.startsWith("@")) {
195                            bundleId = bundleId.substring(1);
196                            harness.deployTestContrib(bundleId, contrib.substring(i + 1));
197                        } else {
198                            harness.deployContrib(bundleId, contrib.substring(i + 1));
199                        }
200                    } else {
201                        harness.deployBundle(contrib);
202                    }
203                }
204            }
205            // use false to prevent removing the local test method deployments
206            Framework.getRuntime().getComponentManager().refresh(false);
207        }
208
209        public void restart() {
210            Framework.getRuntime().getComponentManager().restart(false);
211        }
212
213        public void reset() {
214            Framework.getRuntime().getComponentManager().restart(true);
215        }
216
217        public void reload() {
218            ComponentManager componentManager = Framework.getRuntime().getComponentManager();
219            componentManager.standby();
220            componentManager.resume();
221        }
222
223    }
224
225}