/*
* Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* bstefanescu
*/
package org.eclipse.ecr.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 org.eclipse.ecr.runtime.model.RegistrationInfo;
import org.eclipse.ecr.runtime.model.RuntimeContext;
import org.eclipse.ecr.runtime.osgi.OSGiRuntimeService;
import org.nuxeo.common.Environment;
import org.nuxeo.common.utils.FileUtils;
import org.osgi.framework.Bundle;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
* 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<RegistrationInfo>());
}
public File getRoot() {
return root;
}
public final RuntimeContext getContext(String symbolicName) throws Exception {
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 Exception {
RegistrationInfoImpl ri = (RegistrationInfoImpl)rc.deploy(file.toURI().toURL());
ri.isPersistent = true;
}
public void loadPersistedComponents() throws Exception {
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 Exception {
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 Exception {
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 Exception {
byte[] bytes = safeReadFile(file);
return loadXml(new ByteArrayInputStream(bytes));
}
public static Document loadXml(InputStream in) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
DocumentBuilder builder = factory.newDocumentBuilder();
return builder.parse(in);
}
public void createComponent(byte[] bytes) throws Exception {
createComponent(bytes, true);
}
public synchronized void createComponent(byte[] bytes, boolean isPersistent) throws Exception {
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 Exception {
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.writeFile(file, bytes);
} finally {
fileLock.writeLock().unlock();
}
}
protected byte[] safeReadFile(File file) throws IOException {
fileLock.readLock().lock();
try {
return FileUtils.readBytes(file);
} finally {
fileLock.readLock().unlock();
}
}
}