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    protected BlobProviderDescriptorRegistry blobProviderDescriptorsRegistry = new BlobProviderDescriptorRegistry();
056
057    protected Map<String, BlobProvider> blobProviders = new HashMap<>();
058
059    protected static class BlobProviderDescriptorRegistry extends SimpleContributionRegistry<BlobProviderDescriptor> {
060
061        @Override
062        public String getContributionId(BlobProviderDescriptor contrib) {
063            return contrib.name;
064        }
065
066        @Override
067        public BlobProviderDescriptor clone(BlobProviderDescriptor orig) {
068            return new BlobProviderDescriptor(orig);
069        }
070
071        @Override
072        public void merge(BlobProviderDescriptor src, BlobProviderDescriptor dst) {
073            dst.merge(src);
074        }
075
076        @Override
077        public boolean isSupportingMerge() {
078            return true;
079        }
080
081        public void clear() {
082            currentContribs.clear();
083        }
084
085        public BlobProviderDescriptor getBlobProviderDescriptor(String id) {
086            return getCurrentContribution(id);
087        }
088
089        public Set<String> getBlobProviderIds() {
090            return currentContribs.keySet();
091        }
092    }
093
094    @Override
095    public void deactivate(ComponentContext context) {
096        blobProviderDescriptorsRegistry.clear();
097        // close each blob provider
098        for (BlobProvider blobProvider : blobProviders.values()) {
099            blobProvider.close();
100        }
101        blobProviders.clear();
102    }
103
104    @Override
105    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
106        if (XP.equals(extensionPoint)) {
107            if (contribution instanceof BlobProviderDescriptor) {
108                registerBlobProvider((BlobProviderDescriptor) contribution);
109            } else {
110                throw new NuxeoException("Invalid descriptor: " + contribution.getClass());
111            }
112        } else {
113            throw new NuxeoException("Invalid extension point: " + extensionPoint);
114        }
115    }
116
117    @Override
118    public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
119        if (XP.equals(extensionPoint)) {
120            if (contribution instanceof BlobProviderDescriptor) {
121                unregisterBlobProvider((BlobProviderDescriptor) contribution);
122            }
123        }
124    }
125
126    // public for tests
127    public void registerBlobProvider(BlobProviderDescriptor descr) {
128        closeOldBlobProvider(descr.name);
129        blobProviderDescriptorsRegistry.addContribution(descr);
130        // We can't look up the blob provider right now to initialize it, the platform has not started yet
131        // and the blob provider initialization may require a connection that has not been registered yet.
132    }
133
134    // public for tests
135    public void unregisterBlobProvider(BlobProviderDescriptor descr) {
136        closeOldBlobProvider(descr.name);
137        blobProviderDescriptorsRegistry.removeContribution(descr);
138    }
139
140    /**
141     * We're about to change something about a contributed blob provider. Close the old one.
142     */
143    protected synchronized void closeOldBlobProvider(String id) {
144        BlobProvider blobProvider = blobProviders.remove(id);
145        if (blobProvider != null) {
146            blobProvider.close();
147        }
148    }
149
150    @Override
151    public synchronized BlobProvider getBlobProvider(String providerId) {
152        BlobProvider blobProvider = blobProviders.get(providerId);
153        if (blobProvider == null) {
154            BlobProviderDescriptor descr = blobProviderDescriptorsRegistry.getBlobProviderDescriptor(providerId);
155            if (descr == null) {
156                return null;
157            }
158            Class<?> klass = descr.klass;
159            Map<String, String> properties = descr.properties;
160            try {
161                if (BlobProvider.class.isAssignableFrom(klass)) {
162                    @SuppressWarnings("unchecked")
163                    Class<? extends BlobProvider> blobProviderClass = (Class<? extends BlobProvider>) klass;
164                    blobProvider = blobProviderClass.newInstance();
165                } else if (BinaryManager.class.isAssignableFrom(klass)) {
166                    @SuppressWarnings("unchecked")
167                    Class<? extends BinaryManager> binaryManagerClass = (Class<? extends BinaryManager>) klass;
168                    BinaryManager binaryManager = binaryManagerClass.newInstance();
169                    blobProvider = new BinaryBlobProvider(binaryManager);
170                } else {
171                    throw new RuntimeException("Unknown class for blob provider: " + klass);
172                }
173            } catch (ReflectiveOperationException e) {
174                throw new RuntimeException(e);
175            }
176            try {
177                blobProvider.initialize(providerId, properties);
178            } catch (IOException e) {
179                throw new RuntimeException(e);
180            }
181            blobProviders.put(providerId, blobProvider);
182        }
183        return blobProvider;
184    }
185
186    @Override
187    public BlobProvider getBlobProvider(Blob blob) {
188        if (!(blob instanceof ManagedBlob)) {
189            return null;
190        }
191        ManagedBlob managedBlob = (ManagedBlob) blob;
192        return getBlobProvider(managedBlob.getProviderId());
193    }
194
195    @Override
196    public InputStream getStream(Blob blob) throws IOException {
197        BlobProvider blobProvider = getBlobProvider(blob);
198        if (blobProvider == null) {
199            return null;
200        }
201        try {
202            return blobProvider.getStream((ManagedBlob) blob);
203        } catch (IOException e) {
204            // we don't want to crash everything if the remote file cannot be accessed
205            log.error("Failed to access file: " + ((ManagedBlob) blob).getKey(), e);
206            return new ByteArrayInputStream(new byte[0]);
207        }
208    }
209
210    @Override
211    public InputStream getThumbnail(Blob blob) throws IOException {
212        BlobProvider blobProvider = getBlobProvider(blob);
213        if (blobProvider == null) {
214            return null;
215        }
216        return blobProvider.getThumbnail((ManagedBlob) blob);
217    }
218
219    @Override
220    public URI getURI(Blob blob, UsageHint hint, HttpServletRequest servletRequest) throws IOException {
221        BlobProvider blobProvider = getBlobProvider(blob);
222        if (blobProvider == null) {
223            return null;
224        }
225        return blobProvider.getURI((ManagedBlob) blob, hint, servletRequest);
226    }
227
228    @Override
229    public Map<String, URI> getAvailableConversions(Blob blob, UsageHint hint) throws IOException {
230        BlobProvider blobProvider = getBlobProvider(blob);
231        if (blobProvider == null) {
232            return Collections.emptyMap();
233        }
234        return blobProvider.getAvailableConversions((ManagedBlob) blob, hint);
235    }
236
237    @Override
238    public synchronized Map<String, BlobProvider> getBlobProviders() {
239        Set<String> blobProviderIds = blobProviderDescriptorsRegistry.getBlobProviderIds();
240        if (blobProviders.size() != blobProviderIds.size()) {
241            // register all providers
242            for (String id : blobProviderIds) {
243                getBlobProvider(id); // instantiate and initialize
244            }
245        }
246        return blobProviders;
247    }
248
249}