View Javadoc

    1   /*
    2    * Copyright 2004-2009 the Seasar Foundation and the Others.
    3    *
    4    * Licensed under the Apache License, Version 2.0 (the "License");
    5    * you may not use this file except in compliance with the License.
    6    * You may obtain a copy of the License at
    7    *
    8    *     http://www.apache.org/licenses/LICENSE-2.0
    9    *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
    13   * either express or implied. See the License for the specific language
    14   * governing permissions and limitations under the License.
    15   */
    16  package org.seasar.cubby.internal.util;
    17
    18  import static org.seasar.cubby.internal.util.LogMessages.format;
    19
    20  import java.io.BufferedReader;
    21  import java.io.IOException;
    22  import java.io.InputStream;
    23  import java.io.InputStreamReader;
    24  import java.net.URL;
    25  import java.util.ArrayList;
    26  import java.util.Enumeration;
    27  import java.util.Iterator;
    28  import java.util.LinkedHashMap;
    29  import java.util.List;
    30  import java.util.Map;
    31  import java.util.Map.Entry;
    32
    33  import org.slf4j.Logger;
    34  import org.slf4j.LoggerFactory;
    35
    36  /**
    37   * サービスプロバイダロード機構です。
    38   * <p>
    39   * サービスプロバイダは、リソースディレクトリ <code>META-INF/services</code>
    40   * に「プロバイダ構成ファイル」を配置することによって識別されます。
    41   * このファイルの名前は、サービスの型の完全修飾名になります。このファイルには、具象プロバイダクラスの完全修飾名が 1 行に 1
    42   * つずつ記述されます。それぞれの名前を囲む空白文字とタブ文字、および空白行は無視されます。コメント文字は '#' ('\u0023'、NUMBER
    43   * SIGN) です。 行頭にコメント文字が挿入されている場合、その行のすべての文字は無視されます。ファイルは UTF-8 で符号化されている必要があります。
    44   * </p>
    45   *
    46   * @param <S>
    47   *            このローダーによってロードされるサービスの型
    48   * @author baba
    49   */
    50  public class ServiceLoader<S> implements Iterable<S> {
    51
    52  	/** ロガー。 */
    53  	private static final Logger logger = LoggerFactory
    54  			.getLogger(ServiceLoader.class);
    55
    56  	/** リソースディレクトリ。 */
    57  	private static final String PREFIX = "META-INF/services/";
    58
    59  	/** プロバイダのキャッシュ。 */
    60  	private final Map<String, S> providers = new LinkedHashMap<String, S>();
    61
    62  	/** サービスの型。 */
    63  	private final Class<S> service;
    64
    65  	/** クラスローダ。 */
    66  	private final ClassLoader classLoader;
    67
    68  	/**
    69  	 * 指定されたサービスのローダーを構築します。
    70  	 *
    71  	 * @param service
    72  	 *            サービスを表すインターフェイス
    73  	 * @param classLoader
    74  	 *            プロバイダ構成ファイルとプロバイダクラスのロードに使用するクラスローダー
    75  	 */
    76  	private ServiceLoader(final Class<S> service, final ClassLoader classLoader) {
    77  		this.service = service;
    78  		this.classLoader = classLoader;
    79  		reload();
    80  	}
    81
    82  	/**
    83  	 * プロバイダを再ロードします。
    84  	 */
    85  	public void reload() {
    86  		providers.clear();
    87
    88  		final String resourceName = PREFIX + service.getName();
    89  		try {
    90  			final Enumeration<URL> urls = classLoader
    91  					.getResources(resourceName);
    92  			while (urls.hasMoreElements()) {
    93  				final URL url = urls.nextElement();
    94  				for (final String providerClassName : parse(url)) {
    95  					providers.put(providerClassName, null);
    96  				}
    97  			}
    98  		} catch (final IOException e) {
    99  			throw new ServiceLoadingException(e);
    100 		}
    101 	}
    102
    103 	/**
    104 	 * 指定された URL からプロバイダ構成ファイルを読み込み、パースします。
    105 	 *
    106 	 * @param url
    107 	 *            プロバイダ構成ファイルの URL
    108 	 * @return プロバイダを実装したクラスの <code>List</code>
    109 	 * @throws IOException
    110 	 *             プロバイダ構成ファイルの読み込み時に {@link IOException} が発生した場合
    111 	 */
    112 	private List<String> parse(final URL url) throws IOException {
    113 		if (logger.isDebugEnabled()) {
    114 			logger.debug(format("DCUB0017", service, url));
    115 		}
    116 		final List<String> providerClassNames = new ArrayList<String>();
    117 		InputStream input = null;
    118 		BufferedReader reader = null;
    119 		try {
    120 			input = url.openStream();
    121 			reader = new BufferedReader(new InputStreamReader(input, "utf-8"));
    122 			for (String line; (line = reader.readLine()) != null;) {
    123 				final String providerClassName = cleanup(line);
    124 				if (providerClassName != null) {
    125 					providerClassNames.add(providerClassName);
    126 				}
    127 			}
    128 		} finally {
    129 			if (reader != null) {
    130 				reader.close();
    131 			}
    132 			if (input != null) {
    133 				input.close();
    134 			}
    135 		}
    136 		return providerClassNames;
    137 	}
    138
    139 	/**
    140 	 * 指定された文字列からコメントや空白を取り除きます。
    141 	 *
    142 	 * @param line
    143 	 *            文字列
    144 	 * @return <code>line</code> からコメントや空白を取り除いた文字列
    145 	 */
    146 	private String cleanup(String line) {
    147 		final int commentIndex = line.indexOf('#');
    148 		if (commentIndex >= 0) {
    149 			line = line.substring(0, commentIndex);
    150 		}
    151 		line = line.trim();
    152 		if (line.length() == 0) {
    153 			return null;
    154 		}
    155 		return line;
    156 	}
    157
    158 	/**
    159 	 * プロバイダの {@link Iterator} を取得します。
    160 	 *
    161 	 * @return プロバイダの {@link Iterator}
    162 	 */
    163 	public Iterator<S> iterator() {
    164 		return new ProviderIterator();
    165 	}
    166
    167 	/**
    168 	 * 指定された型とクラスローダーに対応する新しいサービスローダーを作成します。
    169 	 *
    170 	 * @param <S>
    171 	 *            サービスの型
    172 	 * @param service
    173 	 *            サービスの型
    174 	 * @param classLoader
    175 	 *            クラスローダー
    176 	 * @return サービスローダー
    177 	 */
    178 	public static <S> ServiceLoader<S> load(final Class<S> service,
    179 			final ClassLoader classLoader) {
    180 		return new ServiceLoader<S>(service, classLoader);
    181 	}
    182
    183 	/**
    184 	 * 指定された型に対応する新しいサービスローダーを作成します。
    185 	 *
    186 	 * @param <S>
    187 	 *            サービスの型
    188 	 * @param service
    189 	 *            サービスの型
    190 	 * @return サービスローダー
    191 	 */
    192 	public static <S> ServiceLoader<S> load(final Class<S> service) {
    193 		final ClassLoader classLoader = Thread.currentThread()
    194 				.getContextClassLoader();
    195 		return load(service, classLoader);
    196 	}
    197
    198 	/**
    199 	 * プロバイダの {@link Iterator}
    200 	 *
    201 	 * @author baba
    202 	 */
    203 	private class ProviderIterator implements Iterator<S> {
    204
    205 		/** プロバイダのキャッシュの {@link Iterator} */
    206 		private final Iterator<Entry<String, S>> providerIterator;
    207
    208 		/**
    209 		 * 新しい <code>ProviderIterator</code> を構築します。
    210 		 */
    211 		ProviderIterator() {
    212 			providerIterator = providers.entrySet().iterator();
    213 		}
    214
    215 		/**
    216 		 * {@inheritDoc}
    217 		 */
    218 		public boolean hasNext() {
    219 			return providerIterator.hasNext();
    220 		}
    221
    222 		/**
    223 		 * {@inheritDoc}
    224 		 */
    225 		public S next() {
    226 			final Entry<String, S> entry = providerIterator.next();
    227 			if (entry.getValue() == null) {
    228 				final String providerClassName = entry.getKey();
    229 				final S provider = newInstance(providerClassName);
    230 				entry.setValue(provider);
    231 				if (logger.isDebugEnabled()) {
    232 					logger.debug(format("DCUB0018", service, providerClassName,
    233 							provider));
    234 				}
    235 			}
    236 			return entry.getValue();
    237 		}
    238
    239 		/**
    240 		 * 指定されたクラスのインスタンスを生成します。
    241 		 *
    242 		 * @param className
    243 		 *            クラス名
    244 		 * @return 生成したインスタンス
    245 		 */
    246 		private S newInstance(final String className) {
    247 			try {
    248 				final Class<?> providerClass = Class.forName(className, true,
    249 						classLoader);
    250 				final Object providerInstance = providerClass.newInstance();
    251 				final S provider = service.cast(providerInstance);
    252 				return provider;
    253 			} catch (final ClassNotFoundException e) {
    254 				throw new ServiceLoadingException(e);
    255 			} catch (final InstantiationException e) {
    256 				throw new ServiceLoadingException(e);
    257 			} catch (final IllegalAccessException e) {
    258 				throw new ServiceLoadingException(e);
    259 			} catch (final ClassCastException e) {
    260 				throw new ServiceLoadingException(e);
    261 			}
    262 		}
    263
    264 		/**
    265 		 * {@inheritDoc}
    266 		 */
    267 		public void remove() {
    268 			throw new UnsupportedOperationException();
    269 		}
    270
    271 	}
    272
    273 }