/* * Copyright 2008-2014 the original author or authors. * * 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 org.kaleidofoundry.core.naming; import static org.kaleidofoundry.core.i18n.InternalBundleHelper.NamingMessageBundle; import static org.kaleidofoundry.core.naming.NamingConstants.JndiNamingPluginName; import static org.kaleidofoundry.core.naming.NamingContextBuilder.EnvPrefixName; import static org.kaleidofoundry.core.naming.NamingContextBuilder.FailoverEnabled; import static org.kaleidofoundry.core.naming.NamingContextBuilder.FailoverMaxRetry; import static org.kaleidofoundry.core.naming.NamingContextBuilder.FailoverWaitBeforeRetry; import java.util.Properties; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NameNotFoundException; import javax.naming.NamingException; import javax.rmi.PortableRemoteObject; import org.kaleidofoundry.core.context.RuntimeContext; import org.kaleidofoundry.core.lang.annotation.NotNull; import org.kaleidofoundry.core.lang.annotation.Task; import org.kaleidofoundry.core.plugin.Declare; import org.kaleidofoundry.core.util.StringHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * JNDI naming service implementation * * @author jraduget */ @Declare(value = JndiNamingPluginName, description = "jndi naming service implementation") public class JndiNamingService implements NamingService { protected static final Logger logger = LoggerFactory.getLogger(JndiNamingService.class); private final RuntimeContext<NamingService> context; /** * @param context * @throws NamingServiceException */ public JndiNamingService(final RuntimeContext<NamingService> context) throws NamingServiceException { this.context = context; } /** * don't use it, * this constructor is only needed and used by some IOC framework like spring. */ JndiNamingService() { context = null; } /* * (non-Javadoc) * @see org.kaleidofoundry.core.naming.NamingService#lookup(java.lang.String, java.lang.Class) */ @Override @Task(comment = "handle caching policies for home|all") public <R> R locate(@NotNull final String resourceName, @NotNull final Class<R> ressourceClass) throws NamingServiceException { final String jndiEnvNamePrefix = context.getString(EnvPrefixName); // optional java comp env prefix final StringBuilder fullResourceName = new StringBuilder(); // full resource name // we prefix resource name if needed if (!StringHelper.isEmpty(jndiEnvNamePrefix) && !resourceName.startsWith(jndiEnvNamePrefix)) { fullResourceName.append(jndiEnvNamePrefix); if (!jndiEnvNamePrefix.endsWith("/")) { fullResourceName.append("/"); } } fullResourceName.append(resourceName); // lookup & check cast Context intialContext = null; final Object resource; try { // feed init context properties (can be empty) intialContext = createInitialContext(context); // lookup the resource if (isFailoverEnabled()) { resource = lookupWithFailover(intialContext, fullResourceName.toString()); } else { resource = intialContext.lookup(fullResourceName.toString()); } // cast check of the result object (remote or local) try { return ressourceClass.cast(PortableRemoteObject.narrow(resource, ressourceClass)); } catch (final ClassCastException cce) { throw new NamingServiceException("naming.jndi.error.initialContext.lookup.classcast", fullResourceName.toString(), ressourceClass.getName(), resource.getClass().getName()); } } catch (final NamingException nae) { throw handleNamingException(nae, fullResourceName.toString()); } finally { try { closeInitialContext(intialContext, context); } catch (final NamingException ne) { throw new NamingServiceException("naming.jndi.error.initialContext.close", ne, fullResourceName.toString(), ressourceClass.getName()); } } } /** * @param ne * @param resourceName * @return i18n exception conversion from NamingException parameter */ protected NamingServiceException handleNamingException(final NamingException ne, final String resourceName) { // direct not found resource if (ne instanceof NameNotFoundException) { return new NamingServiceNotFoundException(resourceName, (NameNotFoundException) ne, context); } // cause is not found resource if (ne instanceof NamingException && ne.getCause() != null && ne.getCause() instanceof NameNotFoundException) { return new NamingServiceNotFoundException( resourceName, (NameNotFoundException) ne.getCause(), context); } // otherwise ... return new NamingServiceException("naming.jndi.error.initialContext.lookup", ne, resourceName, ne.getMessage(), context.toString()); } /** * @param context * @return context instance build using runtime context properties * @throws NamingException */ @Task(comment = "handle caching policies context|all") protected static Context createInitialContext(final RuntimeContext<NamingService> context) throws NamingException { final Context initialContext = new InitialContext(); final Properties ctxProperties = context.toProperties(); for (final String propertyName : ctxProperties.stringPropertyNames()) { final Object propertyValue = ctxProperties.getProperty(propertyName); if (propertyValue != null) { initialContext.addToEnvironment(propertyName, propertyValue); // map context properties to java naming context properties String mappedPropName = NamingContextBuilder.CONTEXT_PARAMETER_MAPPER.get(propertyName); if (mappedPropName != null) { initialContext.addToEnvironment(mappedPropName, propertyValue); } } } return initialContext; } /** * @param intialContext * @param context * @throws NamingException */ @Task(comment = "handle caching policies context|all - if cacheable do not close") protected static void closeInitialContext(final Context intialContext, final RuntimeContext<NamingService> context) throws NamingException { // free initial context if (intialContext != null) { intialContext.close(); } } /** * @return current instance context */ RuntimeContext<NamingService> getContext() { return context; } /** * @return does failover is enabled * @see NamingContextBuilder#FailoverEnabled */ protected boolean isFailoverEnabled() { return context.getBoolean(FailoverEnabled, false); } /** * @return max attempt after failure * @see NamingContextBuilder#FailoverMaxRetry */ protected int getFailoverMaxRetry() { final Integer maxRetry = context.getInteger(FailoverMaxRetry); if (maxRetry != null) { return maxRetry.intValue(); } else { return -1; } } /** * @return time to sleep after failure * @see NamingContextBuilder#FailoverWaitBeforeRetry */ protected int getFailoverWaitBeforeRetry() { final Integer sleepOnFailure = context.getInteger(FailoverWaitBeforeRetry); if (sleepOnFailure != null) { return sleepOnFailure.intValue(); } else { return 0; } } /** * jndi lookup using if configure a failover processing * * @param intialContext * @param resourceName * @return the lookup resource * @throws NamingException */ protected Object lookupWithFailover(final Context intialContext, final String resourceName) throws NamingException { int retryCount = 0; int maxRetryCount = 1; NamingException lastError = null; while (retryCount < maxRetryCount) { try { return intialContext.lookup(resourceName); } catch (final NamingException nae) { lastError = nae; maxRetryCount = getFailoverMaxRetry(); // no fail-over, we throw the exception if (maxRetryCount <= 0) { throw nae; } // wait for the configuring delay (in milliseconds) else { retryCount++; final int sleepTime = getFailoverWaitBeforeRetry(); if (retryCount < maxRetryCount) { logger.warn(NamingMessageBundle.getMessage("naming.failover.retry.get.info", resourceName, sleepTime, retryCount, maxRetryCount)); try { Thread.sleep((sleepTime)); } catch (final InterruptedException e) { logger.error(NamingMessageBundle.getMessage("naming.failover.retry.error", sleepTime), nae); throw nae; } } else { logger.error(NamingMessageBundle.getMessage("naming.failover.retry.get.info", resourceName, sleepTime, retryCount, maxRetryCount), nae); } } } } if (lastError != null) { throw lastError; } else { throw new IllegalStateException(); } } }