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}