/** * Copyright 2011-2017 Asakusa Framework Team. * * 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.asakusafw.windgate.retryable; import java.io.IOException; import java.text.MessageFormat; import java.util.Map; import com.asakusafw.windgate.core.WindGateLogger; import com.asakusafw.windgate.core.process.ProcessProfile; import com.asakusafw.windgate.core.process.ProcessProvider; import com.asakusafw.windgate.core.util.PropertiesUtil; /** * A structured profile for {@link RetryableProcessProvider}. * @since 0.2.4 * @since 0.5.0 */ public class RetryableProcessProfile { static final WindGateLogger WGLOG = new RetryableProcessLogger(RetryableProcessProfile.class); private static final char SEPARATOR = '.'; /** * The profile key name of {@link #getRetryCount()}. */ public static final String KEY_RETRY_COUNT = "retryCount"; /** * The profile key name of {@link #getRetryInterval()}. * @since 0.5.0 */ public static final String KEY_RETRY_INTERVAL = "retryInterval"; /** * The default {@link #KEY_RETRY_INTERVAL}. * @since 0.5.0 */ public static final long DEFAULT_RETRY_INTERVAL = 0L; /** * The profile key name of {@link #getComponent() component process provider class name}. */ public static final String KEY_COMPONENT = "component"; /** * The profile key prefix of {@link #getComponent() component process provider configuration}. */ public static final String PREFIX_COMPONENT = KEY_COMPONENT + SEPARATOR; private final ProcessProvider component; private final int retryCount; private final long retryInterval; /** * Creates a new instance. * @param component the component process provider to be retried * @param retryCount the retry count * @throws IllegalArgumentException if some parameters were {@code null} */ public RetryableProcessProfile(ProcessProvider component, int retryCount) { this(component, retryCount, 0L); } /** * Creates a new instance. * @param component the component process provider to be retried * @param retryCount the retry count * @param retryInterval the retry interval (in second) * @throws IllegalArgumentException if some parameters were {@code null} * @since 0.5.0 */ public RetryableProcessProfile(ProcessProvider component, int retryCount, long retryInterval) { if (component == null) { throw new IllegalArgumentException("component must not be null"); //$NON-NLS-1$ } if (retryCount <= 0) { throw new IllegalArgumentException("retryCount must be >= 1"); //$NON-NLS-1$ } if (retryInterval < 0) { throw new IllegalArgumentException("retryInterval must be >= 0"); //$NON-NLS-1$ } this.component = component; this.retryCount = retryCount; this.retryInterval = retryInterval; } /** * Converts {@link ProcessProfile} into {@link RetryableProcessProfile}. * @param profile target profile * @return the converted profile * @throws IOException if failed to create a component process provider * @throws IllegalArgumentException if profile is not valid, or any parameter is {@code null} */ public static RetryableProcessProfile convert(ProcessProfile profile) throws IOException { if (profile == null) { throw new IllegalArgumentException("profile must not be null"); //$NON-NLS-1$ } int retryCount = extractInt(profile, KEY_RETRY_COUNT, 1); long retryInterval = extractLong(profile, KEY_RETRY_INTERVAL, DEFAULT_RETRY_INTERVAL); String componentName = profile.getName() + '-' + KEY_COMPONENT; String componentClassName = extract(profile, KEY_COMPONENT, false); Class<? extends ProcessProvider> componentClass; try { Class<?> aClass = Class.forName(componentClassName, false, profile.getContext().getClassLoader()); componentClass = aClass.asSubclass(ProcessProvider.class); } catch (Exception e) { WGLOG.error(e, "E00001", profile.getName(), KEY_COMPONENT, componentClassName); throw new IllegalArgumentException(MessageFormat.format( "Failed to create component process provider for \"{0}\": {1}", profile.getName(), componentClassName), e); } Map<String, String> conf = profile.getConfiguration(); Map<String, String> componentConf = PropertiesUtil.createPrefixMap(conf, PREFIX_COMPONENT); ProcessProfile componentProfile = new ProcessProfile( componentName, componentClass, profile.getContext(), componentConf); ProcessProvider component; try { component = componentProfile.createProvider(); } catch (IOException e) { WGLOG.error(e, "E00001", profile.getName(), KEY_COMPONENT, componentClassName); throw e; } return new RetryableProcessProfile(component, retryCount, retryInterval); } private static String extract(ProcessProfile profile, String configKey, boolean mandatory) { assert profile != null; assert configKey != null; String value = profile.getConfiguration().get(configKey); if (value == null) { if (mandatory == false) { return null; } else { WGLOG.error("E00001", profile.getName(), configKey, null); throw new IllegalArgumentException(MessageFormat.format( "Resource \"{0}\" must declare \"{1}\"", profile.getName(), configKey)); } } try { return profile.getContext().getContextParameters().replace(value.trim(), true); } catch (IllegalArgumentException e) { WGLOG.error(e, "E00001", profile.getName(), configKey, value); throw new IllegalArgumentException(MessageFormat.format( "Failed to resolve environment variables: {2} (resource={0}, property={1})", profile.getName(), configKey, value), e); } } private static int extractInt(ProcessProfile profile, String key, int minimumValue) { assert profile != null; assert key != null; String valueString = extract(profile, key, true); int value; try { value = Integer.parseInt(valueString.trim()); } catch (NumberFormatException e) { WGLOG.error("E00001", profile.getName(), key, valueString); throw new IllegalArgumentException(MessageFormat.format( "The \"{1}\" must be a valid number: {2} (process={0})", profile.getName(), key, valueString), e); } if (value < minimumValue) { WGLOG.error("E00001", profile.getName(), key, valueString); throw new IllegalArgumentException(MessageFormat.format( "The \"{1}\" must be > 0: {2} (process={0})", profile.getName(), value, valueString)); } return value; } private static long extractLong(ProcessProfile profile, String key, long defaultValue) { assert profile != null; assert key != null; String valueString = extract(profile, key, false); if (valueString == null) { return defaultValue; } long value; try { value = Long.parseLong(valueString.trim()); } catch (NumberFormatException e) { WGLOG.error("E00001", profile.getName(), key, valueString); throw new IllegalArgumentException(MessageFormat.format( "The \"{1}\" must be a valid number: {2} (process={0})", profile.getName(), key, valueString), e); } return value; } /** * Returns the target process provider. * @return component process provider */ public ProcessProvider getComponent() { return component; } /** * Returns the max retry count. * @return the retry count */ public int getRetryCount() { return retryCount; } /** * Returns the retry interval. * @return the interval (in second) * @since 0.5.0 */ public long getRetryInterval() { return retryInterval; } }