001/**
002 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
003 *   This file is part of the LDP4j Project:
004 *     http://www.ldp4j.org/
005 *
006 *   Center for Open Middleware
007 *     http://www.centeropenmiddleware.com/
008 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
009 *   Copyright (C) 2014-2016 Center for Open Middleware.
010 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
011 *   Licensed under the Apache License, Version 2.0 (the "License");
012 *   you may not use this file except in compliance with the License.
013 *   You may obtain a copy of the License at
014 *
015 *             http://www.apache.org/licenses/LICENSE-2.0
016 *
017 *   Unless required by applicable law or agreed to in writing, software
018 *   distributed under the License is distributed on an "AS IS" BASIS,
019 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
020 *   See the License for the specific language governing permissions and
021 *   limitations under the License.
022 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
023 *   Artifact    : org.ldp4j.framework:ldp4j-application-kernel-api:0.2.1
024 *   Bundle      : ldp4j-application-kernel-api-0.2.1.jar
025 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
026 */
027package org.ldp4j.application.kernel.spi;
028
029import java.io.File;
030import java.io.FileInputStream;
031import java.io.FileNotFoundException;
032import java.io.IOException;
033import java.io.InputStream;
034import java.lang.reflect.ReflectPermission;
035import java.util.Properties;
036import java.util.ServiceLoader;
037import java.util.concurrent.atomic.AtomicReference;
038
039import org.ldp4j.application.kernel.constraints.ConstraintReportRepository;
040import org.ldp4j.application.kernel.endpoint.EndpointRepository;
041import org.ldp4j.application.kernel.lifecycle.LifecycleException;
042import org.ldp4j.application.kernel.lifecycle.Managed;
043import org.ldp4j.application.kernel.resource.ResourceRepository;
044import org.ldp4j.application.kernel.transaction.TransactionManager;
045import org.slf4j.Logger;
046import org.slf4j.LoggerFactory;
047
048public abstract class RuntimeDelegate implements Managed {
049
050  private static final String INSTANTIATE_ACTION = "instantiate";
051
052  private static final Logger LOGGER=LoggerFactory.getLogger(RuntimeDelegate.class);
053
054  public static final String LDP4J_APPLICATION_SPI_RUNTIMEINSTANCE_FINDER = "org.ldp4j.application.spi.runtimeinstance.finder";
055
056  /**
057   * Name of the configuration file where the
058   * {@link RuntimeDelegate#LDP4J_APPLICATION_SPI_PROPERTY} property that
059   * identifies the {@link RuntimeDelegate} implementation to be returned from
060   * {@link RuntimeDelegate#getInstance()} can be defined.
061   */
062  public static final String LDP4J_APPLICATION_SPI_CFG = "ldp4j-application.properties";
063
064  /**
065   * Name of the property identifying the {@link RuntimeDelegate} implementation
066   * to be returned from {@link RuntimeDelegate#getInstance()}.
067   */
068  public static final String LDP4J_APPLICATION_SPI_PROPERTY = "org.ldp4j.application.spi.RuntimeInstance";
069
070  private static final AtomicReference<RuntimeDelegate> CACHED_DELEGATE=new AtomicReference<RuntimeDelegate>();
071
072  private static ReflectPermission suppressAccessChecksPermission = new ReflectPermission("suppressAccessChecks");
073
074  /**
075   * Allows custom implementations to extend the {@code RuntimeInstance} class.
076   */
077  protected RuntimeDelegate() {
078  }
079
080  /**
081   * Obtain a {@code RuntimeInstance} instance using the method described in
082   * {@link #getInstance}.
083   *
084   * @return an instance of {@code RuntimeInstance}.
085   */
086  private static RuntimeDelegate findDelegate() {
087    try {
088      RuntimeDelegate result=createRuntimeInstanceFromSPI();
089      if(result==null) {
090        result=createRuntimeInstanceFromConfigurationFile();
091      }
092
093      if(result==null) {
094        String delegateClassName = System.getProperty(LDP4J_APPLICATION_SPI_PROPERTY);
095        if(delegateClassName!=null) {
096          result=createRuntimeInstanceForClassName(delegateClassName);
097        }
098      }
099
100      if(result==null) {
101        result=new DefaultRuntimeInstance();
102      }
103
104      return result;
105    } catch (Exception ex) {
106      throw new IllegalStateException("Could not find runtime delegate",ex);
107    }
108  }
109
110  private static RuntimeDelegate createRuntimeInstanceFromConfigurationFile() {
111    RuntimeDelegate result=null;
112    File configFile = getConfigurationFile();
113    if(configFile.canRead()) {
114      InputStream is=null;
115      try {
116        is=new FileInputStream(configFile);
117        Properties configProperties=new Properties();
118        configProperties.load(is);
119        String delegateClassName=configProperties.getProperty(LDP4J_APPLICATION_SPI_PROPERTY);
120        if(delegateClassName!=null) {
121          result=createRuntimeInstanceForClassName(delegateClassName);
122        }
123        if(delegateClassName==null && LOGGER.isWarnEnabled()) {
124          LOGGER.warn("Configuration file '"+configFile.getAbsolutePath()+"' does not define a delegate class name");
125        }
126      } catch(FileNotFoundException e) {
127        if(LOGGER.isDebugEnabled()) {
128          LOGGER.debug("Could not find runtime instance configuration file '"+configFile.getAbsolutePath()+"'",e);
129        }
130      } catch(IOException e) {
131        if(LOGGER.isWarnEnabled()) {
132          LOGGER.warn("Could not load runtime instance configuration file '"+configFile.getAbsolutePath()+"'",e);
133        }
134      } finally {
135        closeQuietly(is, "Could not close configuration properties");
136      }
137    }
138    return result;
139  }
140
141  /**
142   * Get the configuration file for the Runtime Instance: a file named
143   * {@link RuntimeDelegate#LDP4J_APPLICATION_SPI_CFG} in the <code>lib</code> directory of
144   * current JAVA_HOME.
145   *
146   * @return The configuration file for the runtime instance.
147   */
148  private static File getConfigurationFile() {
149    return new File(new File(System.getProperty("java.home")),"lib"+File.separator+LDP4J_APPLICATION_SPI_CFG);
150  }
151
152  /**
153   * Close an input stream logging possible failures.
154   * @param is The input stream that is to be closed.
155   * @param message The message to log in case of failure.
156   */
157  private static void closeQuietly(InputStream is, String message) {
158    if(is!=null) {
159    try {
160      is.close();
161    } catch (Exception e) {
162      if(LOGGER.isWarnEnabled()) {
163        LOGGER.warn(message,e);
164      }
165    }
166    }
167  }
168
169  private static RuntimeDelegate createRuntimeInstanceFromSPI() {
170    if(!"disable".equalsIgnoreCase(System.getProperty(LDP4J_APPLICATION_SPI_RUNTIMEINSTANCE_FINDER))) {
171      for (RuntimeDelegate delegate : ServiceLoader.load(RuntimeDelegate.class)) {
172        return delegate;
173      }
174    }
175    return null;
176  }
177
178  private static RuntimeDelegate createRuntimeInstanceForClassName(String delegateClassName) {
179    RuntimeDelegate result = null;
180    try {
181      Class<?> delegateClass = Class.forName(delegateClassName);
182      if(RuntimeDelegate.class.isAssignableFrom(delegateClass)) {
183        Object impl = delegateClass.newInstance();
184        result = RuntimeDelegate.class.cast(impl);
185      }
186    } catch (ClassNotFoundException e) {
187      handleFailure(delegateClassName, "find", e);
188    } catch (InstantiationException e) {
189      handleFailure(delegateClassName, INSTANTIATE_ACTION, e);
190    } catch (IllegalAccessException e) {
191      handleFailure(delegateClassName, INSTANTIATE_ACTION, e);
192    }
193    return result;
194  }
195
196  /**
197   * @param delegateClassName
198   * @param action
199   * @param failure
200   */
201  private static void handleFailure(String delegateClassName, String action, Exception failure) {
202    if(LOGGER.isWarnEnabled()) {
203      LOGGER.warn("Could not "+action+" delegate class "+delegateClassName,failure);
204    }
205  }
206
207  /**
208   * Obtain a {@code RuntimeInstance} instance. If an instance had not already
209   * been created and set via {@link #setInstance(RuntimeDelegate)}, the first
210   * invocation will create an instance which will then be cached for future
211   * use.
212   *
213   * <p>
214   * The algorithm used to locate the RuntimeInstance subclass to use consists
215   * of the following steps:
216   * </p>
217   * <ul>
218   * <li>
219   * If a resource with the name of
220   * {@code META-INF/services/org.centeropenmiddleware.almistack.poc.clients.spi.RuntimeInstance} exists, then
221   * its first line, if present, is used as the UTF-8 encoded name of the
222   * implementation class.</li>
223   * <li>
224   * If the $java.home/lib/poc-business-logic.properties file exists and it is readable by
225   * the {@code java.util.Properties.load(InputStream)} method and it contains
226   * an entry whose key is {@code org.centeropenmiddleware.almistack.poc.clients.spi.RuntimeInstance}, then the
227   * value of that entry is used as the name of the implementation class.</li>
228   * <li>
229   * If a system property with the name
230   * {@code org.centeropenmiddleware.almistack.poc.clients.spi.RuntimeInstance} is defined, then its value is
231   * used as the name of the implementation class.</li>
232   * <li>
233   * Finally, a default implementation class name is used.</li>
234   * </ul>
235   *
236   * @return an instance of {@code RuntimeInstance}.
237   */
238  public static RuntimeDelegate getInstance() {
239    RuntimeDelegate result = RuntimeDelegate.CACHED_DELEGATE.get();
240    if (result != null) {
241      return result;
242    }
243    synchronized(RuntimeDelegate.CACHED_DELEGATE) {
244      result=RuntimeDelegate.CACHED_DELEGATE.get();
245      if(result==null) {
246        RuntimeDelegate delegate = findDelegate();
247        RuntimeDelegate.CACHED_DELEGATE.set(delegate);
248        result=RuntimeDelegate.CACHED_DELEGATE.get();
249      }
250      return result;
251    }
252  }
253
254  /**
255   * Set the runtime delegate that will be used by Client Business Logic API
256   * classes. If this method is not called prior to {@link #getInstance} then
257   * an implementation will be sought as described in {@link #getInstance}.
258   *
259   * @param delegate
260   *            the {@code RuntimeInstance} runtime delegate instance.
261   * @throws SecurityException
262   *             if there is a security manager and the permission
263   *             ReflectPermission("suppressAccessChecks") has not been
264   *             granted.
265   */
266  public static void setInstance(final RuntimeDelegate delegate) {
267    SecurityManager security = System.getSecurityManager();
268    if (security != null) {
269      security.checkPermission(suppressAccessChecksPermission);
270    }
271    RuntimeDelegate.CACHED_DELEGATE.set(delegate);
272  }
273
274  private static class DefaultRuntimeInstance extends RuntimeDelegate {
275
276    private static final String ERROR_MESSAGE = String.format("No implementation for class '%s' could be found",RuntimeDelegate.class);
277
278    @Override
279    public ModelFactory getModelFactory() {
280      throw new AssertionError(ERROR_MESSAGE);
281    }
282
283    @Override
284    public ConstraintReportRepository getConstraintReportRepository() {
285      throw new AssertionError(ERROR_MESSAGE);
286    }
287
288    @Override
289    public EndpointRepository getEndpointRepository() {
290      throw new AssertionError(ERROR_MESSAGE);
291    }
292
293    @Override
294    public ResourceRepository getResourceRepository() {
295      throw new AssertionError(ERROR_MESSAGE);
296    }
297
298    @Override
299    public TransactionManager getTransactionManager() {
300      throw new AssertionError(ERROR_MESSAGE);
301    }
302
303    @Override
304    public void init() throws LifecycleException {
305      throw new AssertionError(ERROR_MESSAGE);
306    }
307
308    @Override
309    public void shutdown() throws LifecycleException {
310      throw new AssertionError(ERROR_MESSAGE);
311    }
312
313  }
314
315  public abstract ModelFactory getModelFactory();
316
317  public abstract TransactionManager getTransactionManager();
318
319  public abstract ResourceRepository getResourceRepository();
320
321  public abstract EndpointRepository getEndpointRepository();
322
323  public abstract ConstraintReportRepository getConstraintReportRepository();
324
325}