/** * 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.yaess.jobqueue; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.regex.Pattern; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.asakusafw.yaess.core.ExecutionScriptHandler; import com.asakusafw.yaess.core.ServiceProfile; import com.asakusafw.yaess.core.util.PropertiesUtil; import com.asakusafw.yaess.jobqueue.client.HttpJobClient; import com.asakusafw.yaess.jobqueue.client.JobClient; /** * A structured profile for {@link QueueHadoopScriptHandler}. * @since 0.2.6 */ public class JobClientProfile { static final Logger LOG = LoggerFactory.getLogger(JobClientProfile.class); static final String KEY_TIMEOUT = "timeout"; static final String KEY_POLLING_INTERVAL = "pollingInterval"; static final Pattern PATTERN_COMPONENT = Pattern.compile("\\d+"); static final String KEY_URL = "url"; static final String KEY_USER = "user"; static final String KEY_PASSWORD = "password"; static final long DEFAULT_TIMEOUT = 10000; static final long DEFAULT_POLLING_INTERVAL = 1000; private final String prefix; private final List<JobClient> clients; private final long timeout; private final long pollingInterval; /** * Creates a new instance. * @param prefix the profile namespace * @param clients clients * @param timeout timeout duration (ms) * @param pollingInterval polling interval (ms) * @throws IllegalArgumentException if some parameters were {@code null} */ public JobClientProfile(String prefix, List<? extends JobClient> clients, long timeout, long pollingInterval) { if (prefix == null) { throw new IllegalArgumentException("prefix must not be null"); //$NON-NLS-1$ } if (clients == null) { throw new IllegalArgumentException("clients must not be null"); //$NON-NLS-1$ } if (clients.isEmpty()) { throw new IllegalArgumentException("clients must not be empty"); //$NON-NLS-1$ } if (timeout < 0) { throw new IllegalArgumentException("timeout must be >= 0"); //$NON-NLS-1$ } if (pollingInterval <= 0) { throw new IllegalArgumentException("pollingInterval must be >= 0"); //$NON-NLS-1$ } this.prefix = prefix; this.clients = Collections.unmodifiableList(new ArrayList<>(clients)); this.timeout = timeout; this.pollingInterval = pollingInterval; } /** * Returns the current namespace. * @return the prefix */ public String getPrefix() { return prefix; } /** * Returns the job clients. * @return the clients */ public List<JobClient> getClients() { return clients; } /** * Returns the timeout duration. * @return the timeout (ms) */ public long getTimeout() { return timeout; } /** * Returns the polling interval. * @return the polling interval (ms) */ public long getPollingInterval() { return pollingInterval; } /** * Converts general profile into the corresponded this profile. * @param profile general profile * @return this profile * @throws IllegalArgumentException if some parameters were {@code null} */ public static JobClientProfile convert(ServiceProfile<?> profile) { if (profile == null) { throw new IllegalArgumentException("profile must not be null"); //$NON-NLS-1$ } Map<String, String> conf = new HashMap<>(profile.getConfiguration()); conf.remove(ExecutionScriptHandler.KEY_RESOURCE); removeKeyPrefix(conf, ExecutionScriptHandler.KEY_PROP_PREFIX); long timeout = extractLong(profile, conf, KEY_TIMEOUT, DEFAULT_TIMEOUT); if (timeout <= 0) { throw new IllegalArgumentException(MessageFormat.format( "Request time out must be > 0 ({0}.{1}={2})", profile.getPrefix(), KEY_TIMEOUT, timeout)); } long pollingInterval = extractLong(profile, conf, KEY_POLLING_INTERVAL, DEFAULT_POLLING_INTERVAL); if (pollingInterval <= 0) { throw new IllegalArgumentException(MessageFormat.format( "Status polling interval must be > 0 ({0}.{1}={2})", profile.getPrefix(), KEY_TIMEOUT, pollingInterval)); } List<JobClient> clients = extractClients(profile, conf); if (clients.isEmpty()) { throw new IllegalArgumentException(MessageFormat.format( "There must be one or more job clients ({0}.<n>)", profile.getPrefix())); } return new JobClientProfile(profile.getPrefix(), clients, timeout, pollingInterval); } private static long extractLong( ServiceProfile<?> profile, Map<String, String> conf, String key, long defaultValue) { assert profile != null; assert conf != null; assert key != null; String value = profile.normalize(key, conf.remove(key), false, true); if (value == null) { return defaultValue; } try { return Long.parseLong(value); } catch (RuntimeException e) { throw new IllegalArgumentException(MessageFormat.format( "{0}.{1} must be an integer ({2})", profile.getPrefix(), key, value)); } } private static List<JobClient> extractClients( ServiceProfile<?> profile, Map<String, String> conf) { assert profile != null; Set<String> keys = PropertiesUtil.getChildKeys(conf, "", "."); Map<Integer, JobClient> results = new TreeMap<>(); for (String key : keys) { if (isClientPrefix(key) == false) { throw new IllegalArgumentException(MessageFormat.format( "Unknown profile: {0}.{1}", profile.getPrefix(), key)); } int number = Integer.parseInt(key); Map<String, String> subconf = PropertiesUtil.createPrefixMap(conf, key + "."); String prefix = profile.getPrefix() + "." + key; String url = resolve(profile, subconf, prefix, KEY_URL); if (url == null) { throw new IllegalArgumentException(MessageFormat.format( "Target URL is not specified: {0}.{1}", prefix, KEY_URL)); } String user = resolve(profile, subconf, prefix, KEY_USER); String password = resolve(profile, subconf, prefix, KEY_PASSWORD); if (user == null || user.isEmpty()) { results.put(number, new HttpJobClient(url)); } else { password = password == null ? "" : password; results.put(number, new HttpJobClient(url, user, password)); } } return new ArrayList<>(results.values()); } private static String resolve( ServiceProfile<?> profile, Map<String, String> conf, String prefix, String key) { assert profile != null; assert conf != null; assert prefix != null; assert key != null; String value = conf.get(key); if (value == null) { return null; } return resolve(profile, prefix + "." + key, value); } private static String resolve(ServiceProfile<?> profile, String key, String value) { assert profile != null; assert key != null; assert value != null; try { return profile.getContext().getContextParameters().replace(value, true); } catch (IllegalArgumentException e) { throw new IllegalArgumentException(MessageFormat.format( "Failed to resolve {0} ({1})", key, value), e); } } private static boolean isClientPrefix(String key) { assert key != null; return PATTERN_COMPONENT.matcher(key).matches(); } private static void removeKeyPrefix(Map<?, ?> properties, String prefix) { assert properties != null; assert prefix != null; for (Iterator<?> iter = properties.keySet().iterator(); iter.hasNext();) { Object key = iter.next(); if ((key instanceof String) == false) { continue; } String name = (String) key; if (name.startsWith(prefix)) { iter.remove(); } } } }