/** * Copyright © 2013 enioka. All rights reserved * * Licensed 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 com.enioka.jqm.api; import java.io.IOException; import java.net.URL; import java.security.InvalidParameterException; import java.util.Enumeration; import java.util.LinkedHashSet; import java.util.Properties; import java.util.Set; /** * The entry point to create a {@link JqmClient} that will be able to interact with JQM.<br> * {@link JqmClient}s should never be created outside of this factory.<br> * The factory also holds the client cache - clients are cached to avoid creating useless objects and connections. (it is possible to create * a non-cached client but this is not the default) */ public final class JqmClientFactory { private static String STATIC_CLIENT_BINDER_PATH = "com/enioka/jqm/api/StaticClientBinder.class"; private static String STATIC_CLIENT_BINDER_NAME = "com.enioka.jqm.api.StaticClientBinder"; private static IClientFactoryBinder binder; private static Properties props = new Properties(); private JqmClientFactory() { // Utility class } /** * Most client providers use a specific configuration file (such as persistence.xml for the Hibernate provider). However, it may be * desired to overload these values with runtime values. This method enables a client to specify these values.<br> * <strong>Note that the parameter names depend on the provider!</strong><br> * Moreover, changing the properties only impact <code>JqmClient</code>s created after the modification. It is important to keep in mind * that created <code>JqmClient</code>s are cached - therefore it is customary to use this function <strong>before creating any * clients</strong>. * * @param properties * a non null property bag * @throws InvalidParameterException * if props is null */ public static void setProperties(Properties properties) { if (props == null) { throw new InvalidParameterException("props cannot be null"); } JqmClientFactory.props = properties; } public static void setProperty(String key, String value) { if (props == null) { throw new IllegalStateException("properties are null"); } props.setProperty(key, value); } private static final Set<URL> findClientBinders() { Set<URL> res = new LinkedHashSet<URL>(); try { ClassLoader loggerFactoryClassLoader = JqmClientFactory.class.getClassLoader(); Enumeration<URL> paths; if (loggerFactoryClassLoader == null) { paths = ClassLoader.getSystemResources(STATIC_CLIENT_BINDER_PATH); } else { paths = loggerFactoryClassLoader.getResources(STATIC_CLIENT_BINDER_PATH); } while (paths.hasMoreElements()) { URL path = paths.nextElement(); res.add(path); } } catch (IOException ioe) { throw new JqmClientException("Error getting resources from path", ioe); } return res; } private static final void bind() { Set<URL> staticClientBinderPathSet = findClientBinders(); if (staticClientBinderPathSet.isEmpty()) { throw new JqmClientException("there was no client implementation on the classpath"); } if (staticClientBinderPathSet.size() > 1) { throw new JqmClientException("there were multiple client implementations on the classpath. Only keep the one you want to use"); } try { @SuppressWarnings("unchecked") Class<IClientFactoryBinder> binderClass = (Class<IClientFactoryBinder>) JqmClientFactory.class.getClassLoader() .loadClass(STATIC_CLIENT_BINDER_NAME); binder = (IClientFactoryBinder) binderClass.getMethod("getSingleton").invoke(null); } catch (ClassNotFoundException e) { // Should never happen... } catch (Exception e) { throw new JqmClientException("could not load the client provider", e); } } /** * Return the default client. Note this client is shared in the static context. (said otherwise: the same client is always returned * inside a same class loading context). The initialization cost is only paid at first call. * * @return the default client */ public static JqmClient getClient() { return getClient(null, null, true); } /** * Return a new client that may be cached or not. Given properties are always use when not cached, and only used at creation time for * cached clients. * * @param name * if null, default client. Otherwise, helpful to retrieve cached clients later. * @param p * a set of properties. Implementation specific. Unknown properties are silently ignored. * @param cached * if false, the client will not be cached and subsequent calls with the same name will return different objects. */ public static JqmClient getClient(String name, Properties p, boolean cached) { Properties p2 = null; if (binder == null) { bind(); } if (p == null) { p2 = props; } else { p2 = new Properties(props); p2.putAll(p); } return binder.getClientFactory().getClient(name, p2, cached); } /** * Remove the client of the given name from the static cache. Next time {@link #getClient(String, Properties, boolean)} is called, * initialization cost will have to be paid once again.<br> * Use <code>null</code> to reset the default client.<br> * This method is mostly useful for tests when databases are reset and therefore clients become invalid as they hold connections to * them.<br> * If the name does not exist, no exception is thrown. * * @param name * the client to reset, or <code>null</code> for the default client */ public static void resetClient(String name) { if (binder == null) { bind(); } binder.getClientFactory().resetClient(name); } /** * This is {@link #resetClient(String)} with name = null. */ public static void resetClient() { resetClient(null); } }