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