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}