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; 028 029import javax.servlet.http.HttpServletRequest; 030 031import org.apache.commons.logging.Log; 032import org.apache.commons.logging.LogFactory; 033import org.nuxeo.ecm.core.api.Blob; 034import org.nuxeo.ecm.core.api.NuxeoException; 035import org.nuxeo.ecm.core.blob.binary.BinaryBlobProvider; 036import org.nuxeo.ecm.core.blob.binary.BinaryManager; 037import org.nuxeo.runtime.model.ComponentContext; 038import org.nuxeo.runtime.model.ComponentInstance; 039import org.nuxeo.runtime.model.DefaultComponent; 040import org.nuxeo.runtime.model.SimpleContributionRegistry; 041 042/** 043 * Implementation of the service managing the storage and retrieval of {@link Blob}s, through internally-registered 044 * {@link BlobProvider}s. 045 * 046 * @since 7.2 047 */ 048public class BlobManagerComponent extends DefaultComponent implements BlobManager { 049 050 private static final Log log = LogFactory.getLog(BlobManagerComponent.class); 051 052 protected static final String XP = "configuration"; 053 054 protected BlobProviderDescriptorRegistry blobProviderDescriptorsRegistry = new BlobProviderDescriptorRegistry(); 055 056 protected Map<String, BlobProvider> blobProviders = new HashMap<>(); 057 058 protected static class BlobProviderDescriptorRegistry extends SimpleContributionRegistry<BlobProviderDescriptor> { 059 060 @Override 061 public String getContributionId(BlobProviderDescriptor contrib) { 062 return contrib.name; 063 } 064 065 @Override 066 public BlobProviderDescriptor clone(BlobProviderDescriptor orig) { 067 return new BlobProviderDescriptor(orig); 068 } 069 070 @Override 071 public void merge(BlobProviderDescriptor src, BlobProviderDescriptor dst) { 072 dst.merge(src); 073 } 074 075 @Override 076 public boolean isSupportingMerge() { 077 return true; 078 } 079 080 public void clear() { 081 currentContribs.clear(); 082 } 083 084 public BlobProviderDescriptor getBlobProviderDescriptor(String id) { 085 return getCurrentContribution(id); 086 } 087 } 088 089 @Override 090 public void deactivate(ComponentContext context) { 091 blobProviderDescriptorsRegistry.clear(); 092 // close each blob provider 093 for (BlobProvider blobProvider : blobProviders.values()) { 094 blobProvider.close(); 095 } 096 blobProviders.clear(); 097 } 098 099 @Override 100 public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 101 if (XP.equals(extensionPoint)) { 102 if (contribution instanceof BlobProviderDescriptor) { 103 registerBlobProvider((BlobProviderDescriptor) contribution); 104 } else { 105 throw new NuxeoException("Invalid descriptor: " + contribution.getClass()); 106 } 107 } else { 108 throw new NuxeoException("Invalid extension point: " + extensionPoint); 109 } 110 } 111 112 @Override 113 public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { 114 if (XP.equals(extensionPoint)) { 115 if (contribution instanceof BlobProviderDescriptor) { 116 unregisterBlobProvider((BlobProviderDescriptor) contribution); 117 } 118 } 119 } 120 121 // public for tests 122 public void registerBlobProvider(BlobProviderDescriptor descr) { 123 closeOldBlobProvider(descr.name); 124 blobProviderDescriptorsRegistry.addContribution(descr); 125 // lookup now to have immediate feedback on eror 126 getBlobProvider(descr.name); 127 } 128 129 // public for tests 130 public void unregisterBlobProvider(BlobProviderDescriptor descr) { 131 closeOldBlobProvider(descr.name); 132 blobProviderDescriptorsRegistry.removeContribution(descr); 133 } 134 135 /** 136 * We're about to change something about a contributed blob provider. Close the old one. 137 */ 138 protected synchronized void closeOldBlobProvider(String id) { 139 BlobProvider blobProvider = blobProviders.remove(id); 140 if (blobProvider != null) { 141 blobProvider.close(); 142 } 143 } 144 145 @Override 146 public synchronized BlobProvider getBlobProvider(String providerId) { 147 BlobProvider blobProvider = blobProviders.get(providerId); 148 if (blobProvider == null) { 149 BlobProviderDescriptor descr = blobProviderDescriptorsRegistry.getBlobProviderDescriptor(providerId); 150 if (descr == null) { 151 return null; 152 } 153 Class<?> klass = descr.klass; 154 Map<String, String> properties = descr.properties; 155 try { 156 if (BlobProvider.class.isAssignableFrom(klass)) { 157 @SuppressWarnings("unchecked") 158 Class<? extends BlobProvider> blobProviderClass = (Class<? extends BlobProvider>) klass; 159 blobProvider = blobProviderClass.newInstance(); 160 } else if (BinaryManager.class.isAssignableFrom(klass)) { 161 @SuppressWarnings("unchecked") 162 Class<? extends BinaryManager> binaryManagerClass = (Class<? extends BinaryManager>) klass; 163 BinaryManager binaryManager = binaryManagerClass.newInstance(); 164 blobProvider = new BinaryBlobProvider(binaryManager); 165 } else { 166 throw new RuntimeException("Unknown class for blob provider: " + klass); 167 } 168 } catch (ReflectiveOperationException e) { 169 throw new RuntimeException(e); 170 } 171 try { 172 blobProvider.initialize(providerId, properties); 173 } catch (IOException e) { 174 throw new RuntimeException(e); 175 } 176 blobProviders.put(providerId, blobProvider); 177 } 178 return blobProvider; 179 } 180 181 @Override 182 public BlobProvider getBlobProvider(Blob blob) { 183 if (!(blob instanceof ManagedBlob)) { 184 return null; 185 } 186 ManagedBlob managedBlob = (ManagedBlob) blob; 187 return getBlobProvider(managedBlob.getProviderId()); 188 } 189 190 @Override 191 public InputStream getStream(Blob blob) throws IOException { 192 BlobProvider blobProvider = getBlobProvider(blob); 193 if (blobProvider == null) { 194 return null; 195 } 196 try { 197 return blobProvider.getStream((ManagedBlob) blob); 198 } catch (IOException e) { 199 // we don't want to crash everything if the remote file cannot be accessed 200 log.error("Failed to access file: " + ((ManagedBlob) blob).getKey(), e); 201 return new ByteArrayInputStream(new byte[0]); 202 } 203 } 204 205 @Override 206 public InputStream getThumbnail(Blob blob) throws IOException { 207 BlobProvider blobProvider = getBlobProvider(blob); 208 if (blobProvider == null) { 209 return null; 210 } 211 return blobProvider.getThumbnail((ManagedBlob) blob); 212 } 213 214 @Override 215 public URI getURI(Blob blob, UsageHint hint, HttpServletRequest servletRequest) throws IOException { 216 BlobProvider blobProvider = getBlobProvider(blob); 217 if (blobProvider == null) { 218 return null; 219 } 220 return blobProvider.getURI((ManagedBlob) blob, hint, servletRequest); 221 } 222 223 @Override 224 public Map<String, URI> getAvailableConversions(Blob blob, UsageHint hint) throws IOException { 225 BlobProvider blobProvider = getBlobProvider(blob); 226 if (blobProvider == null) { 227 return Collections.emptyMap(); 228 } 229 return blobProvider.getAvailableConversions((ManagedBlob) blob, hint); 230 } 231 232 @Override 233 public Map<String, BlobProvider> getBlobProviders() { 234 return blobProviders; 235 } 236 237}