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}