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}