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