/*
* (C) Copyright 2006-2016 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* bstefanescu
*/
package org.nuxeo.runtime.model.impl;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.commons.io.FileUtils;
import org.nuxeo.common.Environment;
import org.nuxeo.runtime.model.RegistrationInfo;
import org.nuxeo.runtime.model.RuntimeContext;
import org.nuxeo.runtime.osgi.OSGiRuntimeService;
import org.osgi.framework.Bundle;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
/**
* Manage persistent components. Persistent components are located in ${nxserver_data_dir}/components directory, and can
* be dynamically removed or registered. After framework startup (after the application was completely started) the
* persistent components are deployed. The layout of the components directory is the following:
*
* <pre>
* components/
* component1.xml
* component2.xml
* ...
* bundle_symbolicName1/
* component1.xml
* component2.xml
* ...
* bundle_symbolicName1/
* ...
* ...
* </pre>
*
* If components are put directly under the root then they will be deployed in the runtime bundle context. If they are
* put in a directory having as name the symbolicName of a bundle in the system, then the component will be deployed in
* that bundle context.
* <p>
* Any files not ending with .xml are ignored. Any directory that doesn't match a bundle symbolic name will be ignored
* too.
* <p>
* Dynamic components must use the following name convention: (it is not mandatory but it is recommended)
* <ul>
* <li>Components deployed in root directory must use as name the file name without the .xml extension.
* <li>Components deployed in a bundle directory must use the relative file path without the .xml extensions.
* </ul>
* Examples: Given the following component files: <code>components/mycomp1.xml</code> and
* <code>components/mybundle/mycomp2.xml</code> the name for <code>mycomp1</code> must be: <code>comp1</code> and for
* <code>mycomp2</code> must be <code>mybundle/mycomp2</code>
* <p>
* This service is working only with {@link OSGiRuntimeService}
*
* @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
*/
public class ComponentPersistence {
protected final File root; // a directory to keep exploded extensions
protected final RuntimeContext sysrc;
protected final OSGiRuntimeService runtime;
protected final ReadWriteLock fileLock;
protected final Set<RegistrationInfo> persistedComponents;
public ComponentPersistence(OSGiRuntimeService runtime) {
this.runtime = runtime;
root = new File(Environment.getDefault().getData(), "components");
fileLock = new ReentrantReadWriteLock();
sysrc = runtime.getContext();
persistedComponents = Collections.synchronizedSet(new HashSet<>());
}
public File getRoot() {
return root;
}
public final RuntimeContext getContext(String symbolicName) {
if (symbolicName == null) {
return sysrc;
}
Bundle bundle = runtime.getBundle(symbolicName);
if (bundle == null) {
return null;
}
return runtime.createContext(bundle);
}
protected void deploy(RuntimeContext rc, File file) throws IOException {
RegistrationInfoImpl ri = (RegistrationInfoImpl) rc.deploy(file.toURI().toURL());
ri.isPersistent = true;
}
public void loadPersistedComponents() throws IOException {
File[] files = root.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
RuntimeContext rc = getContext(file.getName());
if (rc != null) {
loadPersistedComponents(rc, file);
}
} else if (file.isFile() && file.getName().endsWith(".xml")) {
deploy(sysrc, file);
}
}
}
}
public void loadPersistedComponents(RuntimeContext rc, File root) throws IOException {
File[] files = root.listFiles();
if (files != null) {
for (File file : files) {
if (file.isFile() && file.getName().endsWith(".xml")) {
deploy(rc, file);
}
}
}
}
public void loadPersistedComponent(File file) throws IOException {
file = file.getCanonicalFile();
if (file.isFile() && file.getName().endsWith(".xml")) {
File parent = file.getParentFile();
if (root.equals(parent)) {
deploy(sysrc, file);
return;
} else {
String symbolicName = parent.getName();
parent = parent.getParentFile();
if (root.equals(parent)) {
RuntimeContext rc = getContext(symbolicName);
if (rc != null) {
deploy(rc, file);
return;
}
}
}
}
throw new IllegalArgumentException("Invalid component file location or bundle not found");
}
public Document loadXml(File file) throws IOException {
byte[] bytes = safeReadFile(file);
return loadXml(new ByteArrayInputStream(bytes));
}
public static Document loadXml(InputStream in) {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
try {
DocumentBuilder builder = factory.newDocumentBuilder();
return builder.parse(in);
} catch (SAXException | IOException | ParserConfigurationException e) {
throw new RuntimeException(e);
}
}
public void createComponent(byte[] bytes) throws IOException {
createComponent(bytes, true);
}
public synchronized void createComponent(byte[] bytes, boolean isPersistent) throws IOException {
Document doc = loadXml(new ByteArrayInputStream(bytes));
Element root = doc.getDocumentElement();
String name = root.getAttribute("name");
int p = name.indexOf(':');
if (p > -1) {
name = name.substring(p + 1);
}
p = name.indexOf('/');
String owner = null;
if (p > -1) {
owner = name.substring(0, p);
}
DefaultRuntimeContext rc = (DefaultRuntimeContext) getContext(owner);
File file = new File(this.root, name + ".xml");
if (!isPersistent) {
file.deleteOnExit();
}
file.getParentFile().mkdirs();
safeWriteFile(bytes, file);
rc.deploy(file.toURI().toURL());
}
public synchronized boolean removeComponent(String compName) throws IOException {
String path = compName + ".xml";
File file = new File(root, path);
if (!file.isFile()) {
return false;
}
int p = compName.indexOf('/');
String owner = null;
if (p > -1) {
owner = compName.substring(0, p);
}
DefaultRuntimeContext rc = (DefaultRuntimeContext) getContext(owner);
rc.undeploy(file.toURI().toURL());
file.delete();
return true;
}
protected void safeWriteFile(byte[] bytes, File file) throws IOException {
fileLock.writeLock().lock();
try {
FileUtils.writeByteArrayToFile(file, bytes);
} finally {
fileLock.writeLock().unlock();
}
}
protected byte[] safeReadFile(File file) throws IOException {
fileLock.readLock().lock();
try {
return FileUtils.readFileToByteArray(file);
} finally {
fileLock.readLock().unlock();
}
}
}