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    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}