001/* 002 * (C) Copyright 2015 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 * Florent Guillaume 018 */ 019package org.nuxeo.ecm.core.blob; 020 021import java.io.ByteArrayInputStream; 022import java.io.IOException; 023import java.io.InputStream; 024import java.net.URI; 025import java.util.Collections; 026import java.util.HashMap; 027import java.util.Map; 028import java.util.Set; 029 030import javax.servlet.http.HttpServletRequest; 031 032import org.apache.commons.logging.Log; 033import org.apache.commons.logging.LogFactory; 034import org.nuxeo.ecm.core.api.Blob; 035import org.nuxeo.ecm.core.api.NuxeoException; 036import org.nuxeo.ecm.core.blob.binary.BinaryBlobProvider; 037import org.nuxeo.ecm.core.blob.binary.BinaryManager; 038import org.nuxeo.runtime.model.ComponentContext; 039import org.nuxeo.runtime.model.ComponentInstance; 040import org.nuxeo.runtime.model.DefaultComponent; 041import org.nuxeo.runtime.model.SimpleContributionRegistry; 042 043/** 044 * Implementation of the service managing the storage and retrieval of {@link Blob}s, through internally-registered 045 * {@link BlobProvider}s. 046 * 047 * @since 7.2 048 */ 049public class BlobManagerComponent extends DefaultComponent implements BlobManager { 050 051 private static final Log log = LogFactory.getLog(BlobManagerComponent.class); 052 053 protected static final String XP = "configuration"; 054 055 public static final String DEFAULT_ID = "default"; 056 057 /** 058 * Blob providers whose id starts with this prefix are automatically marked transient. 059 * 060 * @since 10.10 061 * @see BlobProvider#isTransient 062 */ 063 public static final String TRANSIENT_ID_PREFIX = "transient"; 064 065 protected BlobProviderDescriptorRegistry blobProviderDescriptorsRegistry = new BlobProviderDescriptorRegistry(); 066 067 protected Map<String, BlobProvider> blobProviders = new HashMap<>(); 068 069 protected static class BlobProviderDescriptorRegistry extends SimpleContributionRegistry<BlobProviderDescriptor> { 070 071 @Override 072 public String getContributionId(BlobProviderDescriptor contrib) { 073 return contrib.name; 074 } 075 076 @Override 077 public BlobProviderDescriptor clone(BlobProviderDescriptor orig) { 078 return new BlobProviderDescriptor(orig); 079 } 080 081 @Override 082 public void merge(BlobProviderDescriptor src, BlobProviderDescriptor dst) { 083 dst.merge(src); 084 } 085 086 @Override 087 public boolean isSupportingMerge() { 088 return true; 089 } 090 091 public void clear() { 092 currentContribs.clear(); 093 } 094 095 public BlobProviderDescriptor getBlobProviderDescriptor(String id) { 096 return getCurrentContribution(id); 097 } 098 099 public Set<String> getBlobProviderIds() { 100 return currentContribs.keySet(); 101 } 102 } 103 104 @Override 105 public void deactivate(ComponentContext context) { 106 blobProviderDescriptorsRegistry.clear(); 107 // close each blob provider 108 for (BlobProvider blobProvider : blobProviders.values()) { 109 blobProvider.close(); 110 } 111 blobProviders.clear(); 112 } 113 114 @Override 115 public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 116 if (XP.equals(extensionPoint)) { 117 if (contribution instanceof BlobProviderDescriptor) { 118 registerBlobProvider((BlobProviderDescriptor) contribution); 119 } else { 120 throw new NuxeoException("Invalid descriptor: " + contribution.getClass()); 121 } 122 } else { 123 throw new NuxeoException("Invalid extension point: " + extensionPoint); 124 } 125 } 126 127 @Override 128 public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 129 if (XP.equals(extensionPoint)) { 130 if (contribution instanceof BlobProviderDescriptor) { 131 unregisterBlobProvider((BlobProviderDescriptor) contribution); 132 } 133 } 134 } 135 136 // public for tests 137 public void registerBlobProvider(BlobProviderDescriptor descr) { 138 closeOldBlobProvider(descr.name); 139 blobProviderDescriptorsRegistry.addContribution(descr); 140 // We can't look up the blob provider right now to initialize it, the platform has not started yet 141 // and the blob provider initialization may require a connection that has not been registered yet. 142 } 143 144 // public for tests 145 public void unregisterBlobProvider(BlobProviderDescriptor descr) { 146 closeOldBlobProvider(descr.name); 147 blobProviderDescriptorsRegistry.removeContribution(descr); 148 } 149 150 /** 151 * We're about to change something about a contributed blob provider. Close the old one. 152 */ 153 protected synchronized void closeOldBlobProvider(String id) { 154 BlobProvider blobProvider = blobProviders.remove(id); 155 if (blobProvider != null) { 156 blobProvider.close(); 157 } 158 } 159 160 @Override 161 public synchronized BlobProvider getBlobProvider(String providerId) { 162 BlobProvider blobProvider = blobProviders.get(providerId); 163 if (blobProvider == null) { 164 BlobProviderDescriptor descr = blobProviderDescriptorsRegistry.getBlobProviderDescriptor(providerId); 165 if (descr == null) { 166 return null; 167 } 168 Class<?> klass = descr.klass; 169 Map<String, String> properties = descr.properties; 170 try { 171 if (BlobProvider.class.isAssignableFrom(klass)) { 172 @SuppressWarnings("unchecked") 173 Class<? extends BlobProvider> blobProviderClass = (Class<? extends BlobProvider>) klass; 174 blobProvider = blobProviderClass.newInstance(); 175 } else if (BinaryManager.class.isAssignableFrom(klass)) { 176 @SuppressWarnings("unchecked") 177 Class<? extends BinaryManager> binaryManagerClass = (Class<? extends BinaryManager>) klass; 178 BinaryManager binaryManager = binaryManagerClass.newInstance(); 179 blobProvider = new BinaryBlobProvider(binaryManager); 180 } else { 181 throw new RuntimeException("Unknown class for blob provider: " + klass); 182 } 183 } catch (ReflectiveOperationException e) { 184 throw new RuntimeException(e); 185 } 186 // make it transient if needed 187 if (providerId.startsWith(TRANSIENT_ID_PREFIX)) { 188 descr.properties.put(BlobProviderDescriptor.TRANSIENT, "true"); 189 } 190 try { 191 blobProvider.initialize(providerId, properties); 192 } catch (IOException e) { 193 throw new RuntimeException(e); 194 } 195 blobProviders.put(providerId, blobProvider); 196 } 197 return blobProvider; 198 } 199 200 @Override 201 public synchronized BlobProvider getBlobProviderWithNamespace(String providerId) { 202 BlobProvider blobProvider = getBlobProvider(providerId); 203 if (blobProvider != null) { 204 return blobProvider; 205 } 206 // create and register a blob provider from the "default" configuration 207 BlobProviderDescriptor defaultDescr = blobProviderDescriptorsRegistry.getBlobProviderDescriptor(DEFAULT_ID); 208 if (defaultDescr == null) { 209 throw new NuxeoException("Missing configuration for default blob provider"); 210 } 211 // copy 212 BlobProviderDescriptor descr = new BlobProviderDescriptor(defaultDescr); 213 // set new name and namespace 214 descr.name = providerId; 215 descr.properties.put(BlobProviderDescriptor.NAMESPACE, providerId); 216 // register and return it 217 registerBlobProvider(descr); 218 return getBlobProvider(providerId); 219 } 220 221 @Override 222 public BlobProvider getBlobProvider(Blob blob) { 223 if (!(blob instanceof ManagedBlob)) { 224 return null; 225 } 226 ManagedBlob managedBlob = (ManagedBlob) blob; 227 return getBlobProvider(managedBlob.getProviderId()); 228 } 229 230 @Override 231 public InputStream getStream(Blob blob) throws IOException { 232 BlobProvider blobProvider = getBlobProvider(blob); 233 if (blobProvider == null) { 234 return null; 235 } 236 try { 237 return blobProvider.getStream((ManagedBlob) blob); 238 } catch (IOException e) { 239 // we don't want to crash everything if the remote file cannot be accessed 240 log.error("Failed to access file: " + ((ManagedBlob) blob).getKey(), e); 241 return new ByteArrayInputStream(new byte[0]); 242 } 243 } 244 245 @Override 246 public InputStream getThumbnail(Blob blob) throws IOException { 247 BlobProvider blobProvider = getBlobProvider(blob); 248 if (blobProvider == null) { 249 return null; 250 } 251 return blobProvider.getThumbnail((ManagedBlob) blob); 252 } 253 254 @Override 255 public URI getURI(Blob blob, UsageHint hint, HttpServletRequest servletRequest) throws IOException { 256 BlobProvider blobProvider = getBlobProvider(blob); 257 if (blobProvider == null) { 258 return null; 259 } 260 return blobProvider.getURI((ManagedBlob) blob, hint, servletRequest); 261 } 262 263 @Override 264 public Map<String, URI> getAvailableConversions(Blob blob, UsageHint hint) throws IOException { 265 BlobProvider blobProvider = getBlobProvider(blob); 266 if (blobProvider == null) { 267 return Collections.emptyMap(); 268 } 269 return blobProvider.getAvailableConversions((ManagedBlob) blob, hint); 270 } 271 272 @Override 273 public synchronized Map<String, BlobProvider> getBlobProviders() { 274 Set<String> blobProviderIds = blobProviderDescriptorsRegistry.getBlobProviderIds(); 275 if (blobProviders.size() != blobProviderIds.size()) { 276 // register all providers 277 for (String id : blobProviderIds) { 278 getBlobProvider(id); // instantiate and initialize 279 } 280 } 281 return blobProviders; 282 } 283 284}