001/*
002 * (C) Copyright 2006-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 *     bstefanescu
018 *     jcarsique
019 *     Kevin Leturc <kleturc@nuxeo.com>
020 */
021package org.nuxeo.connect.update.standalone;
022
023import java.io.File;
024import java.io.FileInputStream;
025import java.io.IOException;
026import java.io.InputStream;
027import java.nio.file.attribute.FileTime;
028import java.util.HashMap;
029import java.util.List;
030import java.util.Map;
031import java.util.Map.Entry;
032import java.util.zip.ZipEntry;
033import java.util.zip.ZipFile;
034
035import org.nuxeo.common.Environment;
036import org.nuxeo.common.xmap.Context;
037import org.nuxeo.common.xmap.XMap;
038import org.nuxeo.common.xmap.XValueFactory;
039import org.nuxeo.connect.update.LocalPackage;
040import org.nuxeo.connect.update.PackageDependency;
041import org.nuxeo.connect.update.PackageException;
042import org.nuxeo.connect.update.PackageState;
043import org.nuxeo.connect.update.PackageType;
044import org.nuxeo.connect.update.PackageUpdateService;
045import org.nuxeo.connect.update.Version;
046import org.nuxeo.connect.update.model.PackageDefinition;
047import org.nuxeo.connect.update.task.Command;
048import org.nuxeo.connect.update.task.standalone.InstallTask;
049import org.nuxeo.connect.update.task.standalone.UninstallTask;
050import org.nuxeo.connect.update.task.standalone.commands.Append;
051import org.nuxeo.connect.update.task.standalone.commands.Config;
052import org.nuxeo.connect.update.task.standalone.commands.Copy;
053import org.nuxeo.connect.update.task.standalone.commands.Delete;
054import org.nuxeo.connect.update.task.standalone.commands.DeployConfigPlaceholder;
055import org.nuxeo.connect.update.task.standalone.commands.DeployPlaceholder;
056import org.nuxeo.connect.update.task.standalone.commands.FlushJaasCachePlaceholder;
057import org.nuxeo.connect.update.task.standalone.commands.FlushPlaceholder;
058import org.nuxeo.connect.update.task.standalone.commands.InstallPlaceholder;
059import org.nuxeo.connect.update.task.standalone.commands.LoadJarPlaceholder;
060import org.nuxeo.connect.update.task.standalone.commands.ParameterizedCopy;
061import org.nuxeo.connect.update.task.standalone.commands.ReloadPropertiesPlaceholder;
062import org.nuxeo.connect.update.task.standalone.commands.UnAppend;
063import org.nuxeo.connect.update.task.standalone.commands.UndeployConfigPlaceholder;
064import org.nuxeo.connect.update.task.standalone.commands.UndeployPlaceholder;
065import org.nuxeo.connect.update.task.standalone.commands.UninstallPlaceholder;
066import org.nuxeo.connect.update.task.standalone.commands.UnloadJarPlaceholder;
067import org.nuxeo.connect.update.task.update.Rollback;
068import org.nuxeo.connect.update.task.update.Update;
069import org.nuxeo.connect.update.xml.FormsDefinition;
070import org.nuxeo.connect.update.xml.PackageDefinitionImpl;
071
072/**
073 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
074 */
075public class StandaloneUpdateService implements PackageUpdateService {
076
077    protected static XMap xmap;
078
079    protected PackagePersistence persistence;
080
081    protected Map<String, Class<? extends Command>> commands;
082
083    public static XMap getXmap() {
084        return xmap;
085    }
086
087    public StandaloneUpdateService(Environment env) throws IOException {
088        // TODO NXP-9086: Add some checks on the environment
089        Environment.setDefault(env);
090        persistence = new PackagePersistence(this);
091        commands = new HashMap<>();
092    }
093
094    @Override
095    public File getDataDir() {
096        return persistence.getRoot();
097    }
098
099    public PackagePersistence getPersistence() {
100        return persistence;
101    }
102
103    @Override
104    public LocalPackage addPackage(File file) throws PackageException {
105        return persistence.addPackage(file);
106    }
107
108    @Override
109    public void removePackage(String id) throws PackageException {
110        persistence.removePackage(id);
111    }
112
113    @Override
114    public LocalPackage getPackage(String id) throws PackageException {
115        return persistence.getPackage(id);
116    }
117
118    @Override
119    public List<LocalPackage> getPackages() throws PackageException {
120        return persistence.getPackages();
121    }
122
123    public static XMap createXmap() {
124        @SuppressWarnings("hiding")
125        XMap xmap = new XMap();
126        xmap.setValueFactory(PackageType.class, new XValueFactory() {
127            @Override
128            public String serialize(Context arg0, Object arg1) {
129                return ((PackageType) arg1).getValue();
130            }
131
132            @Override
133            public Object deserialize(Context arg0, String arg1) {
134                return PackageType.getByValue(arg1);
135            }
136        });
137        xmap.setValueFactory(Version.class, new XValueFactory() {
138            @Override
139            public String serialize(Context arg0, Object arg1) {
140                return arg1.toString();
141            }
142
143            @Override
144            public Object deserialize(Context arg0, String arg1) {
145                return new Version(arg1);
146            }
147        });
148        xmap.setValueFactory(PackageDependency.class, new XValueFactory() {
149
150            @Override
151            public String serialize(Context arg0, Object arg1) {
152                return arg1.toString();
153            }
154
155            @Override
156            public Object deserialize(Context arg0, String arg1) {
157                return new PackageDependency(arg1);
158            }
159        });
160        xmap.register(PackageDefinitionImpl.class);
161        xmap.register(FormsDefinition.class);
162        return xmap;
163    }
164
165    @Override
166    public void initialize() throws PackageException {
167        xmap = createXmap();
168        addCommands();
169        startInstalledPackages();
170    }
171
172    protected void addCommands() {
173        addCommand(Copy.ID, Copy.class);
174        addCommand(Append.ID, Append.class);
175        addCommand(UnAppend.ID, UnAppend.class);
176        addCommand(ParameterizedCopy.ID, ParameterizedCopy.class);
177        addCommand(Delete.ID, Delete.class);
178        addCommand(InstallPlaceholder.ID, InstallPlaceholder.class);
179        addCommand(UninstallPlaceholder.ID, UninstallPlaceholder.class);
180        addCommand(FlushJaasCachePlaceholder.ID, FlushJaasCachePlaceholder.class);
181        addCommand(FlushPlaceholder.ID, FlushPlaceholder.class);
182        addCommand(ReloadPropertiesPlaceholder.ID, ReloadPropertiesPlaceholder.class);
183        addCommand(DeployPlaceholder.ID, DeployPlaceholder.class);
184        addCommand(UndeployPlaceholder.ID, UndeployPlaceholder.class);
185        addCommand(DeployConfigPlaceholder.ID, DeployConfigPlaceholder.class);
186        addCommand(UndeployConfigPlaceholder.ID, UndeployConfigPlaceholder.class);
187        addCommand(LoadJarPlaceholder.ID, LoadJarPlaceholder.class);
188        addCommand(UnloadJarPlaceholder.ID, UnloadJarPlaceholder.class);
189        addCommand(Config.ID, Config.class);
190        addCommand(Update.ID, Update.class);
191        addCommand(Rollback.ID, Rollback.class);
192    }
193
194    @Deprecated
195    @Override
196    public void setPackageState(LocalPackage pkg, int state) throws PackageException {
197        persistence.updateState(pkg.getId(), state);
198        pkg.setState(state);
199    }
200
201    @Override
202    public void setPackageState(LocalPackage pkg, PackageState state) throws PackageException {
203        persistence.updateState(pkg.getId(), state);
204        pkg.setState(state);
205    }
206
207    @Override
208    public void shutdown() throws PackageException {
209        xmap = null;
210    }
211
212    @Override
213    public Command getCommand(String id) throws PackageException {
214        Class<? extends Command> type = commands.get(id);
215        if (type != null) {
216            try {
217                return type.getConstructor().newInstance();
218            } catch (ReflectiveOperationException e) {
219                throw new PackageException("Failed to load command " + id, e);
220            }
221        }
222        return null;
223    }
224
225    @Override
226    public String getDefaultInstallTaskType() {
227        return InstallTask.class.getName();
228    }
229
230    @Override
231    public String getDefaultUninstallTaskType() {
232        return UninstallTask.class.getName();
233    }
234
235    public void addCommand(String id, Class<? extends Command> cmd) {
236        commands.put(id, cmd);
237    }
238
239    public void removeCommand(String id) {
240        commands.remove(id);
241    }
242
243    @Override
244    public LocalPackage getActivePackage(String name) throws PackageException {
245        return persistence.getActivePackage(name);
246    }
247
248    @Override
249    public PackageDefinition loadPackageFromZip(File file) throws PackageException {
250        ZipFile zip = null;
251        try {
252            zip = new ZipFile(file);
253            ZipEntry mfEntry = zip.getEntry(LocalPackage.MANIFEST);
254            InputStream mfStream = zip.getInputStream(mfEntry);
255            return loadPackage(mfStream);
256        } catch (PackageException e) {
257            throw e;
258        } catch (IOException e) {
259            throw new PackageException("Failed to load package definition from zip file: " + file, e);
260        } finally {
261            if (zip != null) {
262                try {
263                    zip.close();
264                } catch (IOException e) {
265                    throw new PackageException("Failed to close package zip: " + file, e);
266                }
267            }
268        }
269    }
270
271    @Override
272    public PackageDefinition loadPackage(File file) throws PackageException {
273        FileInputStream in = null;
274        try {
275            in = new FileInputStream(file);
276            return loadPackage(in);
277        } catch (PackageException e) {
278            throw e;
279        } catch (IOException e) {
280            throw new PackageException("Failed to load XML package definition from file: " + file, e);
281        } finally {
282            try {
283                if (in != null) {
284                    in.close();
285                }
286            } catch (IOException e) {
287                throw new PackageException("Failed to close input stream for " + file, e);
288            }
289        }
290    }
291
292    @Override
293    public PackageDefinition loadPackage(InputStream in) throws PackageException {
294        try {
295            return (PackageDefinition) xmap.load(in);
296        } catch (IOException e) {
297            throw new PackageException("Failed to parse XML package definition", e);
298        }
299    }
300
301    protected void startInstalledPackages() throws PackageException {
302        for (Entry<String, PackageState> entry : persistence.getStates().entrySet()) {
303            if (entry.getValue() == PackageState.INSTALLED) {
304                persistence.updateState(entry.getKey(), PackageState.STARTED);
305            }
306        }
307    }
308
309    @Override
310    public void reset() throws PackageException {
311        persistence.reset();
312    }
313
314    @Override
315    public void restart() throws PackageException {
316        throw new UnsupportedOperationException();
317    }
318
319    @Override
320    public boolean isStarted(String pkgId) {
321        return persistence.getState(pkgId) == PackageState.STARTED;
322    }
323
324    @Override
325    public File getRegistry() {
326        return new File(getDataDir(), "registry.xml");
327    }
328
329    @Override
330    public File getBackupDir() {
331        return new File(getDataDir(), "backup");
332    }
333
334    @Override
335    public FileTime getInstallDate(String id) {
336        return getPersistence().getInstallDate(id);
337    }
338
339}