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}