001/*
002 * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the Eclipse Public License v1.0
006 * which accompanies this distribution, and is available at
007 * http://www.eclipse.org/legal/epl-v10.html
008 *
009 * Contributors:
010 *     bstefanescu
011 */
012package org.nuxeo.runtime.model.impl;
013
014import java.io.ByteArrayInputStream;
015import java.io.File;
016import java.io.IOException;
017import java.io.InputStream;
018import java.net.MalformedURLException;
019import java.util.Collections;
020import java.util.HashSet;
021import java.util.Set;
022import java.util.concurrent.locks.ReadWriteLock;
023import java.util.concurrent.locks.ReentrantReadWriteLock;
024
025import javax.xml.parsers.DocumentBuilder;
026import javax.xml.parsers.DocumentBuilderFactory;
027import javax.xml.parsers.ParserConfigurationException;
028
029import org.nuxeo.common.Environment;
030import org.nuxeo.common.utils.FileUtils;
031import org.nuxeo.runtime.model.RegistrationInfo;
032import org.nuxeo.runtime.model.RuntimeContext;
033import org.nuxeo.runtime.osgi.OSGiRuntimeService;
034import org.osgi.framework.Bundle;
035import org.w3c.dom.Document;
036import org.w3c.dom.Element;
037import org.xml.sax.SAXException;
038
039/**
040 * Manage persistent components. Persistent components are located in ${nxserver_data_dir}/components directory, and can
041 * be dynamically removed or registered. After framework startup (after the application was completely started) the
042 * persistent components are deployed. The layout of the components directory is the following:
043 *
044 * <pre>
045 * components/
046 *     component1.xml
047 *     component2.xml
048 *     ...
049 *     bundle_symbolicName1/
050 *         component1.xml
051 *         component2.xml
052 *         ...
053 *     bundle_symbolicName1/
054 *         ...
055 *     ...
056 * </pre>
057 *
058 * If components are put directly under the root then they will be deployed in the runtime bundle context. If they are
059 * put in a directory having as name the symbolicName of a bundle in the system, then the component will be deployed in
060 * that bundle context.
061 * <p>
062 * Any files not ending with .xml are ignored. Any directory that doesn't match a bundle symbolic name will be ignored
063 * too.
064 * <p>
065 * Dynamic components must use the following name convention: (it is not mandatory but it is recommended)
066 * <ul>
067 * <li>Components deployed in root directory must use as name the file name without the .xml extension.
068 * <li>Components deployed in a bundle directory must use the relative file path without the .xml extensions.
069 * </ul>
070 * Examples: Given the following component files: <code>components/mycomp1.xml</code> and
071 * <code>components/mybundle/mycomp2.xml</code> the name for <code>mycomp1</code> must be: <code>comp1</code> and for
072 * <code>mycomp2</code> must be <code>mybundle/mycomp2</code>
073 * <p>
074 * This service is working only with {@link OSGiRuntimeService}
075 *
076 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
077 */
078public class ComponentPersistence {
079
080    protected final File root; // a directory to keep exploded extensions
081
082    protected final RuntimeContext sysrc;
083
084    protected final OSGiRuntimeService runtime;
085
086    protected final ReadWriteLock fileLock;
087
088    protected final Set<RegistrationInfo> persistedComponents;
089
090    public ComponentPersistence(OSGiRuntimeService runtime) {
091        this.runtime = runtime;
092        root = new File(Environment.getDefault().getData(), "components");
093        fileLock = new ReentrantReadWriteLock();
094        sysrc = runtime.getContext();
095        persistedComponents = Collections.synchronizedSet(new HashSet<RegistrationInfo>());
096    }
097
098    public File getRoot() {
099        return root;
100    }
101
102    public final RuntimeContext getContext(String symbolicName) {
103        if (symbolicName == null) {
104            return sysrc;
105        }
106        Bundle bundle = runtime.getBundle(symbolicName);
107        if (bundle == null) {
108            return null;
109        }
110        return runtime.createContext(bundle);
111    }
112
113    protected void deploy(RuntimeContext rc, File file) throws IOException {
114        RegistrationInfoImpl ri = (RegistrationInfoImpl) rc.deploy(file.toURI().toURL());
115        ri.isPersistent = true;
116    }
117
118    public void loadPersistedComponents() throws IOException {
119        File[] files = root.listFiles();
120        if (files != null) {
121            for (File file : files) {
122                if (file.isDirectory()) {
123                    RuntimeContext rc = getContext(file.getName());
124                    if (rc != null) {
125                        loadPersistedComponents(rc, file);
126                    }
127                } else if (file.isFile() && file.getName().endsWith(".xml")) {
128                    deploy(sysrc, file);
129                }
130            }
131        }
132    }
133
134    public void loadPersistedComponents(RuntimeContext rc, File root) throws IOException {
135        File[] files = root.listFiles();
136        if (files != null) {
137            for (File file : files) {
138                if (file.isFile() && file.getName().endsWith(".xml")) {
139                    deploy(rc, file);
140                }
141            }
142        }
143    }
144
145    public void loadPersistedComponent(File file) throws IOException {
146        file = file.getCanonicalFile();
147        if (file.isFile() && file.getName().endsWith(".xml")) {
148            File parent = file.getParentFile();
149            if (root.equals(parent)) {
150                deploy(sysrc, file);
151                return;
152            } else {
153                String symbolicName = parent.getName();
154                parent = parent.getParentFile();
155                if (root.equals(parent)) {
156                    RuntimeContext rc = getContext(symbolicName);
157                    if (rc != null) {
158                        deploy(rc, file);
159                        return;
160                    }
161                }
162            }
163        }
164        throw new IllegalArgumentException("Invalid component file location or bundle not found");
165    }
166
167    public Document loadXml(File file) throws IOException {
168        byte[] bytes = safeReadFile(file);
169        return loadXml(new ByteArrayInputStream(bytes));
170    }
171
172    public static Document loadXml(InputStream in) {
173        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
174        factory.setNamespaceAware(true);
175        try {
176            DocumentBuilder builder = factory.newDocumentBuilder();
177            return builder.parse(in);
178        } catch (SAXException | IOException | ParserConfigurationException e) {
179            throw new RuntimeException(e);
180        }
181    }
182
183    public void createComponent(byte[] bytes) throws IOException {
184        createComponent(bytes, true);
185    }
186
187    public synchronized void createComponent(byte[] bytes, boolean isPersistent) throws IOException {
188        Document doc = loadXml(new ByteArrayInputStream(bytes));
189        Element root = doc.getDocumentElement();
190        String name = root.getAttribute("name");
191        int p = name.indexOf(':');
192        if (p > -1) {
193            name = name.substring(p + 1);
194        }
195        p = name.indexOf('/');
196        String owner = null;
197        if (p > -1) {
198            owner = name.substring(0, p);
199        }
200        DefaultRuntimeContext rc = (DefaultRuntimeContext) getContext(owner);
201        File file = new File(this.root, name + ".xml");
202        if (!isPersistent) {
203            file.deleteOnExit();
204        }
205        file.getParentFile().mkdirs();
206        safeWriteFile(bytes, file);
207        rc.deploy(file.toURI().toURL());
208    }
209
210    public synchronized boolean removeComponent(String compName) throws IOException {
211        String path = compName + ".xml";
212        File file = new File(root, path);
213        if (!file.isFile()) {
214            return false;
215        }
216        int p = compName.indexOf('/');
217        String owner = null;
218        if (p > -1) {
219            owner = compName.substring(0, p);
220        }
221        DefaultRuntimeContext rc = (DefaultRuntimeContext) getContext(owner);
222        rc.undeploy(file.toURI().toURL());
223        file.delete();
224        return true;
225    }
226
227    protected void safeWriteFile(byte[] bytes, File file) throws IOException {
228        fileLock.writeLock().lock();
229        try {
230            FileUtils.writeFile(file, bytes);
231        } finally {
232            fileLock.writeLock().unlock();
233        }
234    }
235
236    protected byte[] safeReadFile(File file) throws IOException {
237        fileLock.readLock().lock();
238        try {
239            return FileUtils.readBytes(file);
240        } finally {
241            fileLock.readLock().unlock();
242        }
243    }
244
245}