blob: 7c57a641f9e83294e104e7ac097c990943d90af6 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.sling.commons.mime.internal;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.sling.commons.mime.MimeTypeProvider;
import org.apache.sling.commons.mime.MimeTypeService;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleListener;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.Designate;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The <code>MimeTypeServiceImpl</code> is the official implementation of the
* {@link MimeTypeService} interface.
*/
@Component(service = MimeTypeService.class,
property = {
Constants.SERVICE_VENDOR + "=The Apache Software Foundation",
Constants.SERVICE_DESCRIPTION + "=Apache Sling Commons MIME Type Service"
})
@Designate(ocd = MimeTypeServiceImpl.Config.class)
public class MimeTypeServiceImpl implements MimeTypeService, BundleListener {
public static final String CORE_MIME_TYPES = "/META-INF/core_mime.types";
public static final String MIME_TYPES = "/META-INF/mime.types";
@ObjectClassDefinition(name = "Apache Sling Commons MIME Type Service",
description = "The Sling Commons MIME Type Service provides support for " +
"maintaining and configuring MIME Type mappings.")
public @interface Config {
@AttributeDefinition(name = "MIME Types",
description = "Configures additional MIME type mappings in the "+
"traditional mime.types file format: Each property is a blank space separated "+
"list of strings where the first string is the MIME type and the rest of the "+
"strings are filename extensions referring to the MIME type. Using this "+
"property additional MIME type mappings may be defined. Existing MIME type "+
"mappings cannot be redefined and setting such mappings in this property "+
"has no effect. For a list of existing mappings refer to the MIME Types page.")
String[] mime_types();
}
private Logger log = LoggerFactory.getLogger(this.getClass());
private Map<String, String> mimeMap = new HashMap<>();
private Map<String, String> extensionMap = new HashMap<>();
private MimeTypeProvider[] typeProviders;
private List<MimeTypeProvider> typeProviderList = new ArrayList<>();
// --------- MimeTypeService interface
@Override
public String getMimeType(String name) {
if (name == null) {
return null;
}
String ext = name.substring(name.lastIndexOf('.') + 1);
ext = ext.toLowerCase();
String type = this.mimeMap.get(ext);
if (type == null) {
MimeTypeProvider[] mtp = this.getMimeTypeProviders();
for (int i = 0; type == null && i < mtp.length; i++) {
type = mtp[i].getMimeType(ext);
}
}
return type;
}
@Override
public String getExtension(String mimeType) {
if (mimeType == null) {
return null;
}
// compare using lowercase only
mimeType = mimeType.toLowerCase();
String ext = this.extensionMap.get(mimeType);
if (ext == null) {
MimeTypeProvider[] mtp = this.getMimeTypeProviders();
for (int i = 0; ext == null && i < mtp.length; i++) {
ext = mtp[i].getExtension(mimeType);
}
}
return ext;
}
@Override
public void registerMimeType(String mimeType, String... extensions) {
if (mimeType == null || mimeType.length() == 0 || extensions == null
|| extensions.length == 0) {
return;
}
mimeType = mimeType.toLowerCase();
String defaultExtension = extensionMap.get(mimeType);
for (String extension : extensions) {
if (extension != null && extension.length() > 0) {
extension = extension.toLowerCase();
String oldMimeType = mimeMap.get(extension);
if (oldMimeType == null) {
log.debug("registerMimeType: Add mapping "
+ extension + "=" + mimeType);
this.mimeMap.put(extension, mimeType);
if (defaultExtension == null) {
defaultExtension = extension;
}
} else {
log.info(
"registerMimeType: Ignoring mapping " + extension + "="
+ mimeType + ": Mapping " + extension + "="
+ oldMimeType + " already exists");
}
}
}
if (defaultExtension != null) {
this.extensionMap.put(mimeType, defaultExtension);
}
}
@Override
public void registerMimeType(InputStream mimeTabStream) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(
mimeTabStream, "ISO-8859-1"));
String line;
while ((line = br.readLine()) != null) {
// ignore comment lines
if (line.startsWith("#")) {
continue;
}
registerMimeType(line);
}
}
// ---------- SCR implementation -------------------------------------------
@Activate
protected void activate(final BundleContext context, final Config config) {
context.addBundleListener(this);
// register core and default sling mime types
Bundle bundle = context.getBundle();
registerMimeType(bundle.getEntry(CORE_MIME_TYPES));
registerMimeType(bundle.getEntry(MIME_TYPES));
// register maps of existing bundles
Bundle[] bundles = context.getBundles();
for (int i = 0; i < bundles.length; i++) {
if ((bundles[i].getState() & (Bundle.RESOLVED | Bundle.STARTING
| Bundle.ACTIVE | Bundle.STOPPING)) != 0
&& bundles[i].getBundleId() != bundle.getBundleId()) {
this.registerMimeType(bundles[i].getEntry(MIME_TYPES));
}
}
// register configuration properties
if (config.mime_types() != null) {
for (final String configType : config.mime_types()) {
registerMimeType(configType);
}
}
}
@Deactivate
protected void deactivate(final BundleContext context) {
context.removeBundleListener(this);
}
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
protected void bindMimeTypeProvider(MimeTypeProvider mimeTypeProvider) {
synchronized (this.typeProviderList) {
this.typeProviderList.add(mimeTypeProvider);
this.typeProviders = null;
}
}
protected void unbindMimeTypeProvider(MimeTypeProvider mimeTypeProvider) {
synchronized (this.typeProviderList) {
this.typeProviderList.remove(mimeTypeProvider);
this.typeProviders = null;
}
}
// ---------- BundleListener ----------------------------------------------
@Override
public void bundleChanged(BundleEvent event) {
if (event.getType() == BundleEvent.RESOLVED) {
try {
this.registerMimeType(event.getBundle().getEntry(MIME_TYPES));
} catch (IllegalStateException ie) {
log.info("bundleChanged: an issue while registering the mime type occurred");
}
}
}
// ---------- plugin support -----------------------------------------------
public Map<String, String> getMimeMap() {
return mimeMap;
}
public Map<String, String> getExtensionMap() {
return extensionMap;
}
// ---------- internal -----------------------------------------------------
private MimeTypeProvider[] getMimeTypeProviders() {
MimeTypeProvider[] list = this.typeProviders;
if (list == null) {
synchronized (this.typeProviderList) {
this.typeProviders = this.typeProviderList.toArray(new MimeTypeProvider[this.typeProviderList.size()]);
list = this.typeProviders;
}
}
return list;
}
private void registerMimeType(URL mimetypes) {
if (mimetypes != null) {
InputStream ins = null;
try {
ins = mimetypes.openStream();
this.registerMimeType(ins);
} catch (IOException ioe) {
// log but don't actually care
log.warn("An error occurred reading "
+ mimetypes, ioe);
} finally {
if (ins != null) {
try {
ins.close();
} catch (IOException ioe) {
// ignore
}
}
}
}
}
/**
* Splits the <code>line</code> on whitespace an registers the MIME type
* mappings provided the line contains more than one whitespace separated
* fields.
*
* @throws NullPointerException if <code>line</code> is <code>null</code>.
*/
private void registerMimeType(String line) {
String[] parts = line.split("\\s+");
if (parts.length > 1) {
String[] extensions = new String[parts.length - 1];
System.arraycopy(parts, 1, extensions, 0, extensions.length);
this.registerMimeType(parts[0], extensions);
}
}
}