001/*
002 * (C) Copyright 2006-2017O 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 org.nuxeo.runtime.api.Framework;
022
023/**
024 * A dynamic component deployer which enable tests to deploy new contributions after the test was started (i.e. from
025 * inside the test method) The deployer is reloading all the components and reinject the test members.
026 *
027 * @author bogdan
028 */
029public class HotDeployer {
030
031    public static final String DEPLOY_ACTION = "deploy";
032
033    public static final String RESTART_ACTION = "restart";
034
035    public static final String RESET_ACTION = "reset";
036
037    protected FeaturesRunner runner;
038
039    protected RuntimeHarness harness;
040
041    protected ActionHandler head;
042
043    public HotDeployer(FeaturesRunner runner, RuntimeHarness harness) {
044        this.runner = runner;
045        this.harness = harness;
046        this.head = new DefaultDeployHandler();
047    }
048
049    /**
050     * Add a custom deploy action that wraps the default action. You should call next.exec(...) in your action code to
051     * call the next action
052     */
053    public HotDeployer addHandler(ActionHandler action) {
054        action.next = this.head;
055        this.head = action;
056        return this;
057    }
058
059    /**
060     * Remove the given handler if already registered
061     */
062    public boolean removeHandler(ActionHandler handler) {
063        ActionHandler p = null;
064        ActionHandler h = this.head;
065        while (h != null && h != handler) {
066            p = h;
067            h = h.next;
068        }
069        if (h != null) {
070            if (p == null) {
071                this.head = h.next;
072            } else {
073                p.next = h.next;
074            }
075            h.next = null;
076            return true;
077        }
078        return false;
079    }
080
081    /**
082     * Deploy the given list of contributions. The format is [@]bundleId[:componentPath]. If no component path is
083     * specified then the bundle identified by the bundleId part will be deployed. If a componentPath is given
084     * {@link RuntimeHarness#deployContrib(String,String)} will be used to deploy the contribution. If
085     * bundleId:componentPath expression is prefixed by a '@' character then
086     * {@link RuntimeHarness#deployTestContrib(String,String)} will be used to deploy the contribution.
087     */
088    public void deploy(String... contribs) throws Exception {
089        this.head.exec(DEPLOY_ACTION, contribs);
090        reinject();
091    }
092
093    /**
094     * Restart the components and preserve the current registry state.
095     */
096    public void restart() throws Exception {
097        this.head.exec(RESTART_ACTION);
098        reinject();
099    }
100
101    /**
102     * Restart the components and revert to the initial registry snapshot if any.
103     */
104    public void reset() throws Exception {
105        this.head.exec(RESET_ACTION);
106        reinject();
107    }
108
109    public void reinject() {
110        runner.getInjector().injectMembers(runner.getTargetTestInstance());
111    }
112
113    /**
114     * Deploy actions are usually used by features to customize the deployment of the runtime feature (which is using
115     * the DeaultDeployAction)
116     *
117     * @author bogdan
118     */
119    public static abstract class ActionHandler {
120
121        protected ActionHandler next;
122
123        /**
124         * Can wrap another deploy action with custom code. Should call the next action using
125         * <code>next.exec(action, args...)</code>
126         */
127        public abstract void exec(String action, String... args) throws Exception;
128
129    }
130
131    /**
132     * This action has no next action and will deploy the contributions and then reload the component manager
133     *
134     * @author bogdan
135     */
136    protected class DefaultDeployHandler extends ActionHandler {
137
138        @Override
139        public void exec(String action, String... args) throws Exception {
140            if (DEPLOY_ACTION.equals(action)) {
141                deploy(args);
142            } else if (RESTART_ACTION.equals(action)) {
143                restart();
144            } else if (RESET_ACTION.equals(action)) {
145                reset();
146            }
147        }
148
149        public void deploy(String... contribs) throws Exception {
150            if (contribs != null && contribs.length > 0) {
151                for (String contrib : contribs) {
152                    int i = contrib.indexOf(':');
153                    if (i > -1) {
154                        String bundleId = contrib.substring(0, i);
155                        if (bundleId.startsWith("@")) {
156                            bundleId = bundleId.substring(1);
157                            harness.deployTestContrib(bundleId, contrib.substring(i + 1));
158                        } else {
159                            harness.deployContrib(bundleId, contrib.substring(i + 1));
160                        }
161                    } else {
162                        harness.deployBundle(contrib);
163                    }
164                }
165            }
166            // use false to prevent removing the local test method deployments
167            Framework.getRuntime().getComponentManager().refresh(false);
168        }
169
170        public void restart() throws Exception {
171            Framework.getRuntime().getComponentManager().restart(false);
172        }
173
174        public void reset() throws Exception {
175            Framework.getRuntime().getComponentManager().restart(true);
176        }
177
178    }
179
180}