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