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 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        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 = head;
055        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 = head;
065        while (h != null && h != handler) {
066            p = h;
067            h = h.next;
068        }
069        if (h != null) {
070            if (p == null) {
071                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.
085     */
086    public void deploy(String... contribs) throws Exception {
087        head.exec(DEPLOY_ACTION, contribs);
088        reinject();
089    }
090
091    /**
092     * Restart the components and preserve the current registry state.
093     */
094    public void restart() throws Exception {
095        head.exec(RESTART_ACTION);
096        reinject();
097    }
098
099    /**
100     * Restart the components and revert to the initial registry snapshot if any.
101     */
102    public void reset() throws Exception {
103        head.exec(RESET_ACTION);
104        reinject();
105    }
106
107    public void reinject() {
108        runner.getInjector().injectMembers(runner.getTargetTestInstance());
109    }
110
111    /**
112     * Deploy actions are usually used by features to customize the deployment of the runtime feature (which is using
113     * the DeaultDeployAction)
114     *
115     * @author bogdan
116     */
117    public static abstract class ActionHandler {
118
119        protected ActionHandler next;
120
121        /**
122         * Can wrap another deploy action with custom code. Should call the next action using
123         * <code>next.exec(action, args...)</code>
124         */
125        public abstract void exec(String action, String... args) throws Exception;
126
127    }
128
129    /**
130     * This action has no next action and will deploy the contributions and then reload the component manager
131     *
132     * @author bogdan
133     */
134    protected class DefaultDeployHandler extends ActionHandler {
135
136        @Override
137        public void exec(String action, String... args) throws Exception {
138            if (DEPLOY_ACTION.equals(action)) {
139                deploy(args);
140            } else if (RESTART_ACTION.equals(action)) {
141                restart();
142            } else if (RESET_ACTION.equals(action)) {
143                reset();
144            }
145        }
146
147        public void deploy(String... contribs) throws Exception {
148            if (contribs != null && contribs.length > 0) {
149                for (String contrib : contribs) {
150                    int i = contrib.indexOf(':');
151                    if (i > -1) {
152                        String bundleId = contrib.substring(0, i);
153                        if (bundleId.startsWith("@")) {
154                            bundleId = bundleId.substring(1);
155                            harness.deployTestContrib(bundleId, contrib.substring(i + 1));
156                        } else {
157                            harness.deployContrib(bundleId, contrib.substring(i + 1));
158                        }
159                    } else {
160                        harness.deployBundle(contrib);
161                    }
162                }
163            }
164            // use false to prevent removing the local test method deployments
165            Framework.getRuntime().getComponentManager().refresh(false);
166        }
167
168        public void restart() throws Exception {
169            Framework.getRuntime().getComponentManager().restart(false);
170        }
171
172        public void reset() throws Exception {
173            Framework.getRuntime().getComponentManager().restart(true);
174        }
175
176    }
177
178}