/**
* Copyright 2011-2014 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;
}
}