001/* 002 * (C) Copyright 2011-2017 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 * matic 018 * Kevin Leturc <[email protected]> 019 */ 020package org.nuxeo.runtime.tomcat.dev; 021 022import java.io.File; 023import java.lang.reflect.Method; 024import java.util.ArrayList; 025import java.util.List; 026import java.util.Map; 027import java.util.Objects; 028import java.util.stream.Collectors; 029import java.util.stream.Stream; 030 031/** 032 * Invokes the ReloadService by reflection as this module does not have access to the runtime context. 033 * 034 * @author matic 035 * @since 5.5 036 */ 037public class ReloadServiceInvoker { 038 039 protected Object reloadService; 040 041 protected Method deployBundles; 042 043 protected Method undeployBundles; 044 045 /** 046 * Method to run the deployment preprocessor, previously handled by the deployBundle method 047 * 048 * @since 5.6 049 */ 050 protected Method runDeploymentPreprocessor; 051 052 /** 053 * Method to install local web resources, as the deployment preprocessor won't see dev bundles as defined by Nuxeo 054 * IDE. 055 * 056 * @since 5.6 057 * @deprecated since 5.6, use {@link #runDeploymentPreprocessor} instead, also see 058 * org.nuxeo.runtime.reload.ReloadService 059 */ 060 protected Method installWebResources; 061 062 protected Method flush; 063 064 protected Method reload; 065 066 protected Method flushSeam; 067 068 protected Method reloadSeam; 069 070 /** 071 * @since 9.3 072 */ 073 protected Object devReloadBridge; 074 075 /** 076 * @since 9.3 077 */ 078 protected Method reloadBundles; 079 080 /** 081 * @since 9.3 082 */ 083 protected Method getOSGIBundleName; 084 085 public ReloadServiceInvoker(ClassLoader cl) throws ReflectiveOperationException { 086 Class<?> frameworkClass = cl.loadClass("org.nuxeo.runtime.api.Framework"); 087 Class<?> reloadServiceClass = cl.loadClass("org.nuxeo.runtime.reload.ReloadService"); 088 Method getLocalService = frameworkClass.getDeclaredMethod("getLocalService", Class.class); 089 reloadService = getLocalService.invoke(null, reloadServiceClass); 090 deployBundles = reloadServiceClass.getDeclaredMethod("deployBundles", List.class); 091 undeployBundles = reloadServiceClass.getDeclaredMethod("undeployBundles", List.class); 092 runDeploymentPreprocessor = reloadServiceClass.getDeclaredMethod("runDeploymentPreprocessor"); 093 installWebResources = reloadServiceClass.getDeclaredMethod("installWebResources", File.class); 094 flush = reloadServiceClass.getDeclaredMethod("flush"); 095 reload = reloadServiceClass.getDeclaredMethod("reload"); 096 flushSeam = reloadServiceClass.getDeclaredMethod("flushSeamComponents"); 097 reloadSeam = reloadServiceClass.getDeclaredMethod("reloadSeamComponents"); 098 getOSGIBundleName = reloadServiceClass.getDeclaredMethod("getOSGIBundleName", File.class); 099 // instantiate the DevReloadBridge 100 Class<?> devReloadBridgeClass = cl.loadClass("org.nuxeo.runtime.reload.DevReloadBridge"); 101 devReloadBridge = devReloadBridgeClass.newInstance(); 102 reloadBundles = devReloadBridgeClass.getDeclaredMethod("reloadBundles", List.class, List.class); 103 } 104 105 /** 106 * @deprecated since 9.3, use {@link #hotReloadBundles(DevBundle[], DevBundle[])} instead, keep it for backward 107 * compatibility 108 */ 109 @Deprecated 110 public void hotDeployBundles(DevBundle[] bundles) throws ReflectiveOperationException { 111 ClassLoader cl = Thread.currentThread().getContextClassLoader(); 112 try { 113 Thread.currentThread().setContextClassLoader(reloadService.getClass().getClassLoader()); 114 flush(); 115 // rebuild existing war, this will remove previously copied web 116 // resources 117 // commented out for now, see NXP-9642 118 // runDeploymentPreprocessor(); 119 120 // don't use stream here, cause of ReflectiveOperationException 121 boolean hasSeam = false; 122 List<File> files = new ArrayList<>(bundles.length); 123 for (DevBundle bundle : bundles) { 124 if (bundle.getDevBundleType() == DevBundleType.Bundle) { 125 File file = bundle.file(); 126 // fill dev bundle with its OSGI bundle name in order to allow SDK to undeploy them 127 // this is how admin center get bundle name 128 bundle.name = getOSGIBundleName(file); 129 files.add(file); 130 } else { 131 hasSeam = hasSeam || bundle.getDevBundleType() == DevBundleType.Seam; 132 } 133 } 134 // deploy bundles 135 deployBundles(files); 136 // install their web resources 137 for (File file : files) { 138 installWebResources(file); 139 } 140 // check if we need to reload seam 141 if (hasSeam) { 142 reloadSeam(); 143 } 144 reload(); 145 } finally { 146 Thread.currentThread().setContextClassLoader(cl); 147 } 148 } 149 150 /** 151 * @deprecated since 9.3, use {@link #hotReloadBundles(DevBundle[], DevBundle[])} instead, keep it for backward 152 * compatibility 153 */ 154 @Deprecated 155 public void hotUndeployBundles(DevBundle[] bundles) throws ReflectiveOperationException { 156 ClassLoader cl = Thread.currentThread().getContextClassLoader(); 157 try { 158 Thread.currentThread().setContextClassLoader(reloadService.getClass().getClassLoader()); 159 List<String> bundleNames = Stream.of(bundles) 160 .filter(bundle -> bundle.devBundleType == DevBundleType.Bundle) 161 .map(DevBundle::getName) 162 .filter(Objects::nonNull) 163 .collect(Collectors.toList()); 164 // undeploy bundles 165 undeployBundles(bundleNames); 166 // run deployment preprocessor again: this will remove potential 167 // resources that were copied in the war at deploy 168 // commented out for now, see NXP-9642 169 // runDeploymentPreprocessor(); 170 171 // check if we need to flush seam 172 if (Stream.of(bundles).map(DevBundle::getDevBundleType).anyMatch(DevBundleType.Seam::equals)) { 173 flushSeam(); 174 } 175 } finally { 176 Thread.currentThread().setContextClassLoader(cl); 177 } 178 } 179 180 /** 181 * @return the deployed dev bundles 182 * @since 9.3 183 */ 184 public DevBundle[] hotReloadBundles(DevBundle[] devBundlesToUndeploy, DevBundle[] devBundlesToDeploy) 185 throws ReflectiveOperationException { 186 ClassLoader cl = Thread.currentThread().getContextClassLoader(); 187 try { 188 Thread.currentThread().setContextClassLoader(reloadService.getClass().getClassLoader()); 189 190 // Try to not flush in this implementation, also don't reload seam components 191 // Wasn't able to not flush, seam context need it to reload several stuffs, keep it here for now and check 192 // if we can do that just on seam layer 193 // fyi: 194 // - seam use ReloadComponent#lastFlushed() to trigger the refresh 195 // - in jsf ui there's action to trigger a flush (under the user dropdown), try to decouple flush and reload 196 flush(); 197 198 List<String> bundlesNamesToUndeploy = Stream.of(devBundlesToUndeploy) 199 .filter(bundle -> bundle.devBundleType == DevBundleType.Bundle) 200 .map(DevBundle::getName) 201 .filter(Objects::nonNull) 202 .collect(Collectors.toList()); 203 204 // don't use stream here, cause of ReflectiveOperationException 205 List<File> bundlesToDeploy = new ArrayList<>(devBundlesToDeploy.length); 206 for (DevBundle bundle : devBundlesToDeploy) { 207 if (bundle.getDevBundleType() == DevBundleType.Bundle) { 208 File file = bundle.file(); 209 // fill dev bundle with its OSGI bundle name in order to allow SDK to undeploy them 210 // this is how admin center get bundle name 211 bundle.name = getOSGIBundleName(file); 212 bundlesToDeploy.add(file); 213 } 214 } 215 216 // update Nuxeo server 217 Map<String, String> deployedBundles = reloadBundles(bundlesNamesToUndeploy, bundlesToDeploy); 218 return deployedBundles.entrySet().stream().map(entry -> { 219 DevBundle devBundle = new DevBundle(entry.getValue(), DevBundleType.Bundle); 220 devBundle.name = entry.getKey(); 221 return devBundle; 222 }).toArray(DevBundle[]::new); 223 } finally { 224 Thread.currentThread().setContextClassLoader(cl); 225 } 226 } 227 228 /** 229 * @deprecated since 9.3, use {@link #reloadBundles(List, List)} instead. Kept for backward compatibility. 230 */ 231 protected void deployBundles(List<File> files) throws ReflectiveOperationException { 232 deployBundles.invoke(reloadService, files); 233 } 234 235 /** 236 * @deprecated since 9.3, use {@link #reloadBundles(List, List)} instead. Kept for backward compatibility. 237 */ 238 protected void undeployBundles(List<String> bundleNames) throws ReflectiveOperationException { 239 undeployBundles.invoke(reloadService, bundleNames); 240 } 241 242 protected void flush() throws ReflectiveOperationException { 243 flush.invoke(reloadService); 244 } 245 246 protected void flushSeam() throws ReflectiveOperationException { 247 flushSeam.invoke(reloadService); 248 } 249 250 protected void reload() throws ReflectiveOperationException { 251 reload.invoke(reloadService); 252 } 253 254 protected void reloadSeam() throws ReflectiveOperationException { 255 reloadSeam.invoke(reloadService); 256 } 257 258 protected void runDeploymentPreprocessor() throws ReflectiveOperationException { 259 runDeploymentPreprocessor.invoke(reloadService); 260 } 261 262 /** 263 * @since 9.3 264 */ 265 protected String getOSGIBundleName(File file) throws ReflectiveOperationException { 266 return (String) getOSGIBundleName.invoke(reloadService, file); 267 } 268 269 /** 270 * @deprecated since 5.6, use {@link #runDeploymentPreprocessor()} instead, also see 271 * org.nuxeo.runtime.reload.ReloadService 272 */ 273 @Deprecated 274 protected void installWebResources(File file) throws ReflectiveOperationException { 275 installWebResources.invoke(reloadService, file); 276 } 277 278 /** 279 * @since 9.3 280 */ 281 @SuppressWarnings("unchecked") 282 protected Map<String, String> reloadBundles(List<String> bundlesToUndeploy, List<File> bundlesToDeploy) 283 throws ReflectiveOperationException { 284 return (Map<String, String>) reloadBundles.invoke(devReloadBridge, bundlesToUndeploy, bundlesToDeploy); 285 } 286 287}