package com.uber.sdk.android.core.auth;

import android.app.Activity;
import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.v4.util.Pair;

import com.uber.sdk.android.core.R;
import com.uber.sdk.android.core.utils.Utility;
import com.uber.sdk.core.client.SessionConfiguration;

/**
 * Manages migration problems from old style of redirect URI handling to newer version for Custom
 * tabs support.
 *
 * See https://github.com/uber/rides-android-sdk#authentication-migration-version.
 */
class LegacyUriRedirectHandler {

    enum Mode {
        OFF,
        MISCONFIGURED_AUTH_CODE_FLOW,
        MISSING_REDIRECT,
        MISMATCHING_URI;
    }

    private Mode mode = Mode.OFF;

    /**
     * Will validate that the Redirect URI mode is valid {@link Mode#OFF} and return true
     *
     * If false, then the app should terminate codeflow, this will happen in Debug mode for
     * unhandled migration scenarios.
     * See https://github.com/uber/rides-android-sdk#authentication-migration-version.
     *
     * @param activity to lookup package info and launch blocking dialog
     * @param loginManager to validate old auth code flow
     * @return true if valid, false if invalid
     */
    boolean checkValidState(@NonNull Activity activity, @NonNull LoginManager
            loginManager) {
        initState(activity, loginManager);
        boolean validToContinueExecution = true;
        if (isLegacyMode()) {
            String logMessage = activity.getString(getLegacyModeErrorMessage());
            String uiTitle = activity.getString(R.string.ub__misconfigured_redirect_uri_title);
            String uiMessage = activity.getString(R.string.ub__misconfigured_redirect_uri_message);

            validToContinueExecution = !Utility.logAndShowBlockingDebugUIAlert(activity,
                    logMessage, uiTitle, uiMessage, new IllegalStateException(logMessage));
        }
        return validToContinueExecution;
    }

    boolean isLegacyMode() {
        return mode != Mode.OFF;
    }

    private void initState(@NonNull Activity activity, @NonNull LoginManager loginManager) {

        SessionConfiguration sessionConfiguration = loginManager.getSessionConfiguration();
        boolean redirectForAuthorizationCode = loginManager.isRedirectForAuthorizationCode();

        String generatedRedirectUri = activity.getPackageName().concat(".uberauth://redirect");
        String setRedirectUri = sessionConfiguration.getRedirectUri();

        if (redirectForAuthorizationCode) {
            mode = Mode.MISCONFIGURED_AUTH_CODE_FLOW;
        } else if (sessionConfiguration.getRedirectUri() == null) {
            mode = Mode.MISSING_REDIRECT;
        } else if (!generatedRedirectUri.equals(setRedirectUri) &&
                !AuthUtils.isRedirectUriRegistered(activity, Uri.parse(setRedirectUri)) &&
                !loginManager.isAuthCodeFlowEnabled()) {
            mode = Mode.MISMATCHING_URI;
        } else {
            mode = Mode.OFF;
        }

    }

    private Pair<String, String> getLegacyModeMessage(@NonNull Context context, @NonNull
            LoginManager
            loginManager) {

        final Pair<String, String> titleAndMessage;
        switch (mode) {
            case MISCONFIGURED_AUTH_CODE_FLOW:
                titleAndMessage = new Pair<>("Misconfigured Redirect URI - See log.",
                        "The Uber Authentication Flow for the Authorization Code Flow has "
                                + "been upgraded in 0.8.0 and a redirect URI must now be supplied to the application. "
                                + "You are seeing this error because the use of deprecated method "
                                + "LoginManager.setRedirectForAuthorizationCode() indicates your flow may not "
                                + "support the recent changes. See https://github"
                                + ".com/uber/rides-android-sdk#authentication-migration-version"
                                + "-08-and-above for resolution steps"
                                + "to insure your setup is correct and then migrate to the non-deprecate "
                                + "method LoginManager.setAuthCodeFlowEnabled()");
                break;
            case MISSING_REDIRECT:
                titleAndMessage = new Pair<>("Null Redirect URI - See log.",
                        "Redirect URI must be set in "
                        + "Session Configuration.");
                break;
            case MISMATCHING_URI:
                String generatedRedirectUri = context.getPackageName().concat(""
                        + ".uberauth://redirect");
                String setRedirectUri = loginManager.getSessionConfiguration().getRedirectUri();
                titleAndMessage = new Pair<>("Misconfigured Redirect URI - See log.",
                        "Misconfigured redirect_uri. See https://github"
                                + ".com/uber/rides-android-sdk#authentication-migration-version-08-and-above"
                                + "for more info. Either 1) Register " + generatedRedirectUri + " as a "
                                + "redirect uri for the app at https://developer.uber.com/dashboard/ and "
                                + "specify this in your SessionConfiguration or 2) Override the default "
                                + "redirect_uri with the current one set (" + setRedirectUri + ") in the "
                                + "AndroidManifest.");
                break;
            default:
                titleAndMessage = new Pair<>("Unknown URI Redirect Issue", "Unknown issue, see "
                        + "log");
        }

        return titleAndMessage;

    }

    private int getLegacyModeErrorMessage() {
        switch (mode) {
            case MISCONFIGURED_AUTH_CODE_FLOW:
                return R.string.ub__misconfigured_auth_code_flow_log;
            case MISSING_REDIRECT:
                return R.string.ub__missing_redirect_uri_log;
            case MISMATCHING_URI:
                return R.string.ub__mismatching_redirect_uri_log;
            default:
                return 0;
        }
    }
}


/*
 * Copyright (c) 2016 Uber Technologies, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package com.uber.sdk.android.core.auth;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.Base64;
import android.webkit.WebView;

import com.uber.sdk.core.auth.AccessToken;
import com.uber.sdk.core.auth.Scope;
import com.uber.sdk.core.client.SessionConfiguration;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;

/**
 * A utility class for the Uber SDK.
 */
class AuthUtils {
    static final String KEY_AUTHENTICATION_CODE = "code";
    static final String KEY_EXPIRATION_TIME = "expires_in";
    static final String KEY_SCOPES = "scope";
    static final String KEY_TOKEN = "access_token";
    static final String KEY_REFRESH_TOKEN = "refresh_token";
    static final String KEY_TOKEN_TYPE = "token_type";

    /**
     * @param scopeCollection
     * @return true if any {@link com.uber.sdk.core.auth.Scope}s requested is {@link com.uber.sdk.core.auth.Scope.ScopeType#PRIVILEGED}
     */
    static boolean isPrivilegeScopeRequired(@NonNull Collection<Scope> scopeCollection) {
        for (Scope scope : scopeCollection) {
            if (scope.getScopeType().equals(Scope.ScopeType.PRIVILEGED)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Converts a {@link Collection} of {@link Scope}s to a {@link Set} of {@link String}s.
     *
     * @param scopeCollection the {@link Collection} of {@link Scope}s to convert.
     * @return a {@link Set} of {@link String}s.
     */
    @NonNull
    static Set<String> scopeCollectionToStringSet(@NonNull Collection<Scope> scopeCollection) {
        Set<String> stringCollection = new HashSet<>();
        for (Scope scope : scopeCollection) {
            stringCollection.add(scope.name());
        }

        return stringCollection;
    }

    /**
     * Converts a {@link String} representing space delimited {@link Scope}s to a {@link Collection<Scope>}.
     *
     * @param scopesString the {@link String} to convert.
     * @return a {@link Collection} of {@link Scope}s.
     * @throws IllegalArgumentException if a part of the string doesn't match a scope name.
     */
    @NonNull
    static Collection<Scope> stringToScopeCollection(@NonNull String scopesString) throws IllegalArgumentException {
        Set<Scope> scopeCollection = new HashSet<>();

        if (scopesString.isEmpty()) {
            return scopeCollection;
        }

        String[] scopeStrings = scopesString.split(" ");
        for (String scopeName : scopeStrings) {
            try {
                scopeCollection.add(Scope.valueOf(scopeName.toUpperCase()));
            } catch (IllegalArgumentException e) {
                // do nothing, will omit custom or bad scopes
            }
        }

        return scopeCollection;
    }

    /**
     * Converts a {@link Set} of {@link String}s to {@link Collection} of {@link Scope}s.
     *
     * @param stringSet the {@link Set} of {@link String}s to convert.
     * @return a {@link Collection} of {@link Scope}s.
     */
    @NonNull
    static Collection<Scope> stringCollectionToScopeCollection(@NonNull Collection<String> stringSet)
            throws IllegalArgumentException {
        Set<Scope> scopeCollection = new HashSet<>();

        for (String scopeName : stringSet) {
            scopeCollection.add(Scope.valueOf(scopeName));
        }
        return scopeCollection;
    }

    public static boolean isRedirectUriRegistered(@NonNull Activity activity, @NonNull Uri uri) {

        Intent intent = new Intent(Intent.ACTION_VIEW, uri);
        intent.setAction(Intent.ACTION_VIEW);
        intent.setData(uri);
        ComponentName info = intent.resolveActivity(activity.getPackageManager());

        return info != null && info.getClassName().equals(LoginRedirectReceiverActivity.class
                .getName());

    }

    /**
     * Converts a {@link Collection} of {@link Scope}s into a space-delimited {@link String}.
     *
     * @param scopes the {@link Collection} of {@link Scope}s to convert
     * @return a space-delimited {@link String} of {@link Scope}s
     */
    @NonNull
    public static String scopeCollectionToString(@NonNull Collection<Scope> scopes) {
        Set<String> stringSet = scopeCollectionToStringSet(scopes);
        return TextUtils.join(" ", stringSet).toLowerCase();
    }

    /**
     * Converts a {@link Collection} of {@link String}s into a space-delimited {@link String}.
     *
     * @param scopes the {@link Collection} of {@link String}s to convert
     * @return a space-delimited {@link String} of {@link Scope}s
     */
    public static String customScopeCollectionToString(@NonNull Collection<String> scopes) {
        return TextUtils.join(" ", scopes).toLowerCase();
    }

    /**
     *
     * @param scopes array to return as space delimited
     * @return space-delimited {@link String} of Scopes and Custom Scopes
     */
    public static String mergeScopeStrings(String... scopes) {
        return TextUtils.join(" ", scopes).trim();
    }

    @NonNull
    static AccessToken parseTokenUri(@NonNull Uri uri) throws LoginAuthenticationException {
        final long expiresIn;
        try {
            expiresIn = Long.valueOf(uri.getQueryParameter(KEY_EXPIRATION_TIME));
        } catch (NumberFormatException ex) {
            throw new LoginAuthenticationException(AuthenticationError.INVALID_RESPONSE);
        }

        final String accessToken = uri.getQueryParameter(KEY_TOKEN);
        final String refreshToken = uri.getQueryParameter(KEY_REFRESH_TOKEN);
        final String scope = uri.getQueryParameter(KEY_SCOPES);
        final String tokenType = uri.getQueryParameter(KEY_TOKEN_TYPE);

        if (TextUtils.isEmpty(accessToken) || TextUtils.isEmpty(scope) || TextUtils.isEmpty(tokenType)) {
            throw new LoginAuthenticationException(AuthenticationError.INVALID_RESPONSE);
        }

        return new AccessToken(expiresIn, AuthUtils.stringToScopeCollection
                (scope), accessToken, refreshToken, tokenType);
    }

    @NonNull
    static Intent parseTokenUriToIntent(@NonNull Uri uri) throws LoginAuthenticationException {
        final long expiresIn;
        try {
            expiresIn = Long.valueOf(uri.getQueryParameter(KEY_EXPIRATION_TIME));
        } catch (NumberFormatException ex) {
            throw new LoginAuthenticationException(AuthenticationError.INVALID_RESPONSE);
        }

        final String accessToken = uri.getQueryParameter(KEY_TOKEN);
        final String refreshToken = uri.getQueryParameter(KEY_REFRESH_TOKEN);
        final String scope = uri.getQueryParameter(KEY_SCOPES);
        final String tokenType = uri.getQueryParameter(KEY_TOKEN_TYPE);

        if (TextUtils.isEmpty(accessToken) || TextUtils.isEmpty(scope) || TextUtils.isEmpty(tokenType)) {
            throw new LoginAuthenticationException(AuthenticationError.INVALID_RESPONSE);
        }

        Intent data = new Intent();
        data.putExtra(LoginManager.EXTRA_ACCESS_TOKEN, accessToken);
        data.putExtra(LoginManager.EXTRA_REFRESH_TOKEN, refreshToken);
        data.putExtra(LoginManager.EXTRA_SCOPE, scope);
        data.putExtra(LoginManager.EXTRA_EXPIRES_IN, expiresIn);
        data.putExtra(LoginManager.EXTRA_TOKEN_TYPE, tokenType);
        return data;
    }

    static boolean isAuthorizationCodePresent(@NonNull Uri uri) {
        return !TextUtils.isEmpty(uri.getQueryParameter(KEY_AUTHENTICATION_CODE));
    }

    static String parseAuthorizationCode(@NonNull Uri uri) throws LoginAuthenticationException {
        final String code = uri.getQueryParameter(KEY_AUTHENTICATION_CODE);
        if (TextUtils.isEmpty(code)) {
            throw new LoginAuthenticationException(AuthenticationError.INVALID_RESPONSE);
        }

        return code;
    }

    @NonNull
    static AccessToken createAccessToken(Intent intent) {
        String token = intent.getStringExtra(LoginManager.EXTRA_ACCESS_TOKEN);
        String refreshToken = intent.getStringExtra(LoginManager.EXTRA_REFRESH_TOKEN);
        String scope = intent.getStringExtra(LoginManager.EXTRA_SCOPE);
        String tokenType = intent.getStringExtra(LoginManager.EXTRA_TOKEN_TYPE);
        long expiresIn = intent.getLongExtra(LoginManager.EXTRA_EXPIRES_IN, 0);

        return new AccessToken(expiresIn, AuthUtils.stringToScopeCollection
                (scope), token, refreshToken, tokenType);

    }

    static String createEncodedParam(String rawParam) {
        return Base64.encodeToString(rawParam.getBytes(), Base64.DEFAULT);
    }

    /**
     * Builds a URL {@link String} using the necessary parameters to load in the {@link WebView}.
     *
     * @return the URL to load in the {@link WebView}
     */
    @NonNull
    static String buildUrl(
            @NonNull String redirectUri,
            @NonNull ResponseType responseType,
            @NonNull SessionConfiguration configuration) {

        final String CLIENT_ID_PARAM = "client_id";
        final String ENDPOINT = "login";
        final String HTTPS = "https";
        final String PATH = "oauth/v2/authorize";
        final String REDIRECT_PARAM = "redirect_uri";
        final String RESPONSE_TYPE_PARAM = "response_type";
        final String SCOPE_PARAM = "scope";
        final String SHOW_FB_PARAM = "show_fb";
        final String SIGNUP_PARAMS = "signup_params";
        final String REDIRECT_LOGIN = "{\"redirect_to_login\":true}";

        Uri.Builder builder = new Uri.Builder();
        builder.scheme(HTTPS)
                .authority(ENDPOINT + "." + configuration.getEndpointRegion().getDomain())
                .appendEncodedPath(PATH)
                .appendQueryParameter(CLIENT_ID_PARAM, configuration.getClientId())
                .appendQueryParameter(REDIRECT_PARAM, redirectUri)
                .appendQueryParameter(RESPONSE_TYPE_PARAM, responseType.toString().toLowerCase(
                        Locale.US))
                .appendQueryParameter(SCOPE_PARAM, getScopes(configuration))
                .appendQueryParameter(SHOW_FB_PARAM, "false")
                .appendQueryParameter(SIGNUP_PARAMS, AuthUtils.createEncodedParam(REDIRECT_LOGIN));

        return builder.build().toString();
    }

    private static String getScopes(SessionConfiguration configuration) {
        String scopes = AuthUtils.scopeCollectionToString(configuration.getScopes());
        if (!configuration.getCustomScopes().isEmpty()) {
            scopes =  AuthUtils.mergeScopeStrings(scopes,
                    AuthUtils.customScopeCollectionToString(configuration.getCustomScopes()));
        }
        return scopes;
    }
}


package com.epam.mock.web;

import com.epam.mock.model.ConfigurationHandler;
import com.epam.mock.service.ConfigurationHandlerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

/**
 * Response controller class.
 */
@RestController
public class ConfigurationHandlerController {

    private ConfigurationHandlerService configurationHandlerService;

    private static final HttpHeaders httpHeaders;

    static {
        httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_JSON);
    }

    @Autowired
    public void setConfigurationHandlerService(ConfigurationHandlerService configurationHandlerService) {
        this.configurationHandlerService = configurationHandlerService;
    }

    /**
     * Processes HTTP.GET request (URL: .../getUsers).
     * @return ResponseEntity with string in JSON format with all users.
     */
    @RequestMapping("/getUsers")
    public ResponseEntity<String> getUsers() {
        ConfigurationHandler response =  configurationHandlerService.getConfigForGetUsers();

        return new ResponseEntity<>(response.getBodyAnswer(), httpHeaders, HttpStatus.valueOf(response.getResult()));
    }

    /**
     * Processes HTTP.GET request (URL: .../getUser).
     * @return ResponseEntity with string in JSON format with specified user or 'user no found' error.
     */
    @RequestMapping("/getUser")
    public ResponseEntity<String> getUser() {
        ConfigurationHandler response =  configurationHandlerService.getConfigForGetUser();

        return new ResponseEntity<>(response.getBodyAnswer(), httpHeaders, HttpStatus.valueOf(response.getResult()));
    }

    /**
     * Processes HTTP.POST request (URL: .../registerUser).
     * @return ResponseEntity with string in JSON format with http status result (success or not) and user id.
     */
    @RequestMapping(method = RequestMethod.POST, value = "/registerUser")
    public ResponseEntity<String> registerUser() {
        ConfigurationHandler response =  configurationHandlerService.getConfigForRegisterUser();

        return new ResponseEntity<>(response.getBodyAnswer(), httpHeaders, HttpStatus.valueOf(response.getResult()));
    }

    /**
     * Processes HTTP.PUT request (URL: .../updateUser).
     * @return ResponseEntity with http status result (success or not).
     */
    @RequestMapping(method = RequestMethod.PUT, value = "/updateUser")
    public ResponseEntity<Void> updateUser() {
        ConfigurationHandler response =  configurationHandlerService.getConfigForUpdateUser();

        return new ResponseEntity<>(HttpStatus.valueOf(response.getResult()));
    }

    /**
     * Processes HTTP.PATCH request (URL: .../updateUserField).
     * @return ResponseEntity with http status result (success or not).
     */
    @RequestMapping(method = RequestMethod.PATCH, value = "/updateUserField")
    public ResponseEntity<Void> updateUserField() {
        ConfigurationHandler response =  configurationHandlerService.getConfigForUpdateUserField();

        return new ResponseEntity<>(HttpStatus.valueOf(response.getResult()));
    }

    /**
     * Processes HTTP.DELETE request (URL: .../clearUser).
     * @return ResponseEntity with http status result (success or not).
     */
    @RequestMapping(method = RequestMethod.DELETE, value = "/clearUser")
    public ResponseEntity<Void> deleteUser() {
        ConfigurationHandler response =  configurationHandlerService.getConfigForDeleteUser();

        return new ResponseEntity<>(HttpStatus.valueOf(response.getResult()));
    }

    /**
     * Processes HTTP.DELETE request (URL: .../clearAll).
     * @return ResponseEntity with http status result (success or not).
     */
    @RequestMapping(method = RequestMethod.DELETE, value = "/clearAll")
    public ResponseEntity<Void> deleteUsers() {
        ConfigurationHandler response =  configurationHandlerService.getConfigForDeleteUsers();

        return new ResponseEntity<>(HttpStatus.valueOf(response.getResult()));
    }

    /**
     * Edit representation for method specified in sent parameter after processing HTTP.POST request (URL: .../manage).
     * @param configurationHandler Representation of response.
     * @return HttpStatus.OK if contains sent method, HttpStatus.BAD_REQUEST otherwise.
     */
    @RequestMapping(method = RequestMethod.POST, value = "/manage")
    public ResponseEntity<Void> manage(@RequestBody ConfigurationHandler configurationHandler) {
        return configurationHandlerService.editMap(configurationHandler)
                ? new ResponseEntity<>(HttpStatus.OK)
                : new ResponseEntity<>(HttpStatus.BAD_REQUEST);
    }
}


package com.epam.mock.service;

import com.epam.mock.model.ConfigurationHandler;
import com.epam.mock.model.Method;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

/**
 * Response service class.
 */
@Service
public class ConfigurationHandlerService {

    private static Map<Method, ConfigurationHandler> configurationHandlerMap = new HashMap<>();

    static {
        configurationHandlerMap.put(Method.GETUSERS, new ConfigurationHandler("getusers", 200, "{\"name\":\"roman\", \"id\":\"300\",\"role\":\"admin\"}"));
        configurationHandlerMap.put(Method.GETUSER, new ConfigurationHandler("getuser", 200, "{\"name\":\"roman\", \"id\":\"300\",\"role\":\"admin\"}"));
        configurationHandlerMap.put(Method.REGISTERUSER, new ConfigurationHandler("registeruser", 404, ""));
        configurationHandlerMap.put(Method.UPDATEUSER, new ConfigurationHandler("updateuser", 200, ""));
        configurationHandlerMap.put(Method.UPDATEUSERFIELD, new ConfigurationHandler("updateuserfield", 200, ""));
        configurationHandlerMap.put(Method.CLEARUSER, new ConfigurationHandler("clearuser", 404, ""));
        configurationHandlerMap.put(Method.CLEARALL, new ConfigurationHandler("clearall", 200, ""));
    }

    /**
     * Responses to controller call for all users.
     * @return Our representation of response with JSON format.
     */
    public ConfigurationHandler getConfigForGetUsers() {
        return configurationHandlerMap.get(Method.GETUSERS);
    }

    /**
     * Responses to controller call for specified user.
     * @return Our representation of response with JSON format or 'user no found' error.
     */
    public ConfigurationHandler getConfigForGetUser() {
        return configurationHandlerMap.get(Method.GETUSER);
    }

    /**
     * Responses to controller call for adding new user.
     * @return Our representation of response with JSON format  with status result (success or not) and user id.
     */
    public ConfigurationHandler getConfigForRegisterUser() {
        return configurationHandlerMap.get(Method.REGISTERUSER);
    }

    /**
     * Responses to controller call for updating user.
     * @return Our representation of response with status result (success or not).
     */
    public ConfigurationHandler getConfigForUpdateUser() {
        return configurationHandlerMap.get(Method.UPDATEUSER);
    }

    /**
     * Responses to controller call for updating user's field(s).
     * @return Our representation of response with status result (success or not).
     */
    public ConfigurationHandler getConfigForUpdateUserField() {
        return configurationHandlerMap.get(Method.UPDATEUSERFIELD);
    }

    /**
     * Responses to controller call for removing user.
     * @return Our representation of response with status result (success or not).
     */
    public ConfigurationHandler getConfigForDeleteUser() {
        return configurationHandlerMap.get(Method.CLEARUSER);
    }

    /**
     * Responses to controller call for removing all users.
     * @return Our representation of response with status result (success or not).
     */
    public ConfigurationHandler getConfigForDeleteUsers() {
        return configurationHandlerMap.get(Method.CLEARALL);
    }

    /**
     * Edit representation for method specified in sent parameter after controller calls for it.
     * @param configurationHandler Our representation of response.
     * @return True if mock-service contains sent method, false otherwise.
     */
    public boolean editMap(ConfigurationHandler configurationHandler) {
        String method = configurationHandler.getMethod().toUpperCase();

        if (configurationHandlerMap.containsKey(Method.valueOf(method))) {
            configurationHandlerMap.put(Method.valueOf(method), configurationHandler);

            return true;
        }

        return false;
    }
}


/*
 * Copyright 2015 Netflix, Inc.
 *
 * 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.netflix.fenzo.triggers.persistence;

import com.netflix.fenzo.triggers.Trigger;

import java.util.List;
import java.util.UUID;

/**
 * In-memory implementation of {@code TriggerDao}.
 */
public class InMemoryTriggerDao extends AbstractInMemoryDao<Trigger> implements TriggerDao {

    /**
     * @warn method description missing
     * @warn parameter descriptions missing
     *
     * @param triggerGroup
     * @param trigger
     * @return
     */
    @Override
    public String createTrigger(String triggerGroup, Trigger trigger) {
        trigger.setId(createId(triggerGroup, UUID.randomUUID().toString()));
        create(triggerGroup, trigger.getId(), trigger);
        return trigger.getId();
    }

    /**
     * @warn method description missing
     * @warn parameter triggerId description missing
     *
     * @param triggerId
     * @return
     */
    @Override
    public Trigger getTrigger(String triggerId) {
        String triggerGroup = extractGroupFromId(triggerId);
        return read(triggerGroup, triggerId);
    }

    /**
     * @warn method description missing
     * @warn parameter trigger description missing
     *
     * @param trigger
     */
    @Override
    public void updateTrigger(Trigger trigger) {
        String triggerGroup = extractGroupFromId(trigger.getId());
        update(triggerGroup, trigger.getId(), trigger);
    }

    /**
     * @warn method description missing
     * @warn parameter descriptions missing
     *
     * @param triggerGroup
     * @param trigger
     */
    @Override
    public void deleteTrigger(String triggerGroup, Trigger trigger) {
        delete(triggerGroup, trigger.getId());
    }

    /**
     * @warn method description missing
     * @warn parameter triggerGroup description missing
     *
     * @param triggerGroup
     * @return
     */
    @Override
    public List<Trigger> getTriggers(String triggerGroup) {
        return list(triggerGroup);
    }

    /**
     * @warn method description missing
     *
     * @return
     */
    @Override
    public List<Trigger> getTriggers() {
        return list();
    }

}


/*
 * Copyright 2016 Netflix, Inc.
 *
 * 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.netflix.fenzo;

import com.netflix.fenzo.common.ThreadFactoryBuilder;
import com.netflix.fenzo.functions.Action0;
import com.netflix.fenzo.functions.Action1;
import com.netflix.fenzo.functions.Func1;
import com.netflix.fenzo.queues.InternalTaskQueue;
import com.netflix.fenzo.queues.QAttributes;
import com.netflix.fenzo.queues.QueuableTask;
import com.netflix.fenzo.queues.TaskQueue;
import com.netflix.fenzo.queues.TaskQueueException;
import com.netflix.fenzo.queues.TaskQueueMultiException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

/**
 * A task scheduling service that maintains a scheduling loop to continuously assign resources to tasks pending in
 * the queue. This service maintains a scheduling loop to assign resources to tasks in the queue created when
 * constructing this service. It calls {@link TaskScheduler#scheduleOnce(TaskIterator, List)} from within its
 * scheduling loop. Users of this service add tasks into this service's queue, which are held until they are assigned.
 * Here's a typical use of this service:
 *

 *

 *         Build a {@link TaskScheduler} using its builder, {@link TaskScheduler.Builder}.
 *

 *

 *         Build this service using its builder, {@link TaskSchedulingService.Builder}, providing a queue implementation
 *         from {@link com.netflix.fenzo.queues.TaskQueues}. Specify scheduling interval and other callbacks.
 *

 *

 *         Start the scheduling loop by calling {@link #start()}.
 *

 *

 *         Receive callbacks for scheduling result that provide a {@link SchedulingResult} object. Note that it is
 *         not allowed to call {@link TaskScheduler#getTaskAssigner()} for tasks assigned in the result, they are
 *         assigned from within this scheduling service. This service assigns the tasks before making the result
 *         available to you via the callback. The assignments also sets any resource sets requested by the task.
 *         To mark tasks as running for those tasks that were running from before this service was created, use
 *         {@link #initializeRunningTask(QueuableTask, String)}. Later, call
 *         {@link #removeTask(String, QAttributes, String)} when tasks complete or they no longer need resource assignments.
 *

 *

 */
public class TaskSchedulingService {

    private static class RemoveTaskRequest {
        private final String taskId;
        private final QAttributes qAttributes;
        private final String hostname;

        public RemoveTaskRequest(String taskId, QAttributes qAttributes, String hostname) {
            this.taskId = taskId;
            this.qAttributes = qAttributes;
            this.hostname = hostname;
        }
    }

    private static class SetReadyTimeRequest {
        private final String taskId;
        private final QAttributes qAttributes;
        private final long when;

        private SetReadyTimeRequest(String taskId, QAttributes qAttributes, long when) {
            this.taskId = taskId;
            this.qAttributes = qAttributes;
            this.when = when;
        }
    }

    private static final Logger logger = LoggerFactory.getLogger(TaskSchedulingService.class);
    private final TaskScheduler taskScheduler;
    private final Action1<SchedulingResult> schedulingResultCallback;
    private final InternalTaskQueue taskQueue;
    private final ScheduledExecutorService executorService;
    private final long loopIntervalMillis;
    private final Action0 preHook;
    private final BlockingQueue<VirtualMachineLease> leaseBlockingQueue = new LinkedBlockingQueue<>();
    private final BlockingQueue<Map<String, QueuableTask>> addRunningTasksQueue = new LinkedBlockingQueue<>();
    private final BlockingQueue<RemoveTaskRequest> removeTasksQueue = new LinkedBlockingQueue<>();
    private final BlockingQueue<SetReadyTimeRequest> setReadyTimeQueue = new LinkedBlockingQueue<>();
    private final BlockingQueue<Action1<Map<TaskQueue.TaskState, Collection<QueuableTask>>>> taskMapRequest = new LinkedBlockingQueue<>(10);
    private final BlockingQueue<Action1<Map<String, Map<VMResource, Double[]>>>> resStatusRequest = new LinkedBlockingQueue<>(10);
    private final BlockingQueue<Action1<List<VirtualMachineCurrentState>>> vmCurrStateRequest = new LinkedBlockingQueue<>(10);
    private final AtomicLong lastSchedIterationAt = new AtomicLong();
    private final long maxSchedIterDelay;
    private volatile Func1<QueuableTask, List<String>> taskToClusterAutoScalerMapGetter = null;

    private TaskSchedulingService(Builder builder) {
        taskScheduler = builder.taskScheduler;
        schedulingResultCallback = builder.schedulingResultCallback;
        taskQueue = builder.taskQueue;
        taskScheduler.getTaskTracker().setUsageTrackedQueue(taskQueue.getUsageTracker());
        taskScheduler.setUsingSchedulingService(true);
        executorService = builder.executorService;
        loopIntervalMillis = builder.loopIntervalMillis;
        preHook = builder.preHook;
        maxSchedIterDelay = Math.max(builder.maxDelayMillis, loopIntervalMillis);
    }

    /**
     * Start this scheduling service. Tasks are scheduled continuously in a loop with a delay between two consecutive
     * iterations of at least the value specified via {@link Builder#withLoopIntervalMillis(long)}, and at most delay
     * specified via {@link Builder#withMaxDelayMillis(long)}. The delay between consecutive iterations is longer if the
     * service notices no change since the previous iteration. Changes include additions of new tasks and additions of
     * new leases.
     */
    public void start() {
        executorService.scheduleWithFixedDelay(TaskSchedulingService.this::scheduleOnce, 0, loopIntervalMillis, TimeUnit.MILLISECONDS);
    }

    /**
     * Mark this scheduler as shutdown and prevent any further scheduling iterations from starting. This may let an
     * already running scheduling iteration to complete.
     */
    public void shutdown() {
        executorService.shutdown();
    }

    public boolean isShutdown() {
        return executorService.isShutdown();
    }

    /* package */ TaskQueue getQueue() {
        return taskQueue;
    }

    /* package */ Map<String, Integer> requestPseudoScheduling(final InternalTaskQueue pTaskQueue, Map<String, Integer> groupCounts) {
        Map<String, Integer> pseudoSchedulingResult = new HashMap<>();
            try {
                logger.debug("Creating pseudo hosts");
                final Map<String, List<String>> pseudoHosts = taskScheduler.createPseudoHosts(groupCounts);
                logger.debug("Created " + pseudoHosts.size() + " pseudoHost groups");
                int pHostsAdded = 0;
                for(Map.Entry<String, List<String>> entry: pseudoHosts.entrySet()) {
                    logger.debug("Pseudo hosts for group " + entry.getKey() + ": " + entry.getValue());
                    pHostsAdded += entry.getValue() == null? 0 : entry.getValue().size();
                }
                try {
                    Map<String, String> hostnameToGrpMap = new HashMap<>();
                    for (Map.Entry<String, List<String>> entry : pseudoHosts.entrySet()) {
                        for (String h : entry.getValue())
                            hostnameToGrpMap.put(h, entry.getKey());
                    }
                    try {
                        pTaskQueue.reset();
                    } catch (TaskQueueMultiException e) {
                        final List<Exception> exceptions = e.getExceptions();
                        if (exceptions == null || exceptions.isEmpty()) {
                            logger.error("Error with pseudo queue, no details available");
                        } else {
                            logger.error("Error with pseudo queue, details:");
                            for (Exception pe : exceptions) {
                                logger.error("pseudo queue error detail: " + pe.getMessage());
                            }
                        }
                    }
                    // temporarily replace usage tracker in taskTracker to the pseudoQ and then put back the original one
                    taskScheduler.getTaskTracker().setUsageTrackedQueue(pTaskQueue.getUsageTracker());
                    logger.debug("Scheduling with pseudoQ");
                    final SchedulingResult schedulingResult = taskScheduler.pseudoScheduleOnce(pTaskQueue);
                    final Map<String, VMAssignmentResult> resultMap = schedulingResult.getResultMap();
                    Map<String, Integer> result = new HashMap<>();
                    if (!resultMap.isEmpty()) {
                        for (String h : resultMap.keySet()) {
                            final String grp = hostnameToGrpMap.get(h);
                            if (grp != null) {
                                Integer count = result.get(grp);
                                if (count == null)
                                    result.put(grp, 1);
                                else
                                    result.put(grp, count + 1);
                            }
                        }
                    }
                    else if(pHostsAdded > 0) {
                        logger.debug("No pseudo assignments made, looking for failures");
                        final Map<TaskRequest, List<TaskAssignmentResult>> failures = schedulingResult.getFailures();
                        if (failures == null || failures.isEmpty()) {
                            logger.debug("No failures found for pseudo assignments");
                        } else {
                            for (Map.Entry<TaskRequest, List<TaskAssignmentResult>> entry: failures.entrySet()) {
                                final List<TaskAssignmentResult> tars = entry.getValue();
                                if (tars == null || tars.isEmpty())
                                    logger.debug("No pseudo assignment failures for task " + entry.getKey());
                                else {
                                    StringBuilder b = new StringBuilder("Pseudo assignment failures for task ").append(entry.getKey()).append(": ");
                                    for (TaskAssignmentResult r: tars) {
                                        b.append("HOST: ").append(r.getHostname()).append(":");
                                        final List<AssignmentFailure> afs = r.getFailures();
                                        if (afs != null && !afs.isEmpty())
                                            afs.forEach(af -> b.append(af.getMessage()).append("; "));
                                        else
                                            b.append("None").append(";");
                                    }
                                    logger.debug(b.toString());
                                }
                            }
                        }
                    }
                    pseudoSchedulingResult = result;
                }
                catch (Exception e) {
                    logger.error("Error in pseudo scheduling", e);
                    throw e;
                }
                finally {
                    taskScheduler.removePseudoHosts(pseudoHosts);
                    taskScheduler.removePseudoAssignments();
                    taskScheduler.getTaskTracker().setUsageTrackedQueue(taskQueue.getUsageTracker());
                }
            }
            catch (Exception e) {
                logger.error("Error in pseudo scheduling", e);
            }
        return pseudoSchedulingResult;
    }

    private void scheduleOnce() {
        try {
            taskScheduler.checkIfShutdown();
        }
        catch (IllegalStateException e) {
            logger.warn("Shutting down due to taskScheduler being shutdown");
            shutdown();
            return;
        }
        try {
            // check if next scheduling iteration is actually needed right away
            final boolean qModified = taskQueue.reset();
            addPendingRunningTasks();
            removeTasks();
            setTaskReadyTimes();
            final boolean newLeaseExists = leaseBlockingQueue.peek() != null;
            if (qModified || newLeaseExists || doNextIteration()) {
                taskScheduler.setTaskToClusterAutoScalerMapGetter(taskToClusterAutoScalerMapGetter);
                lastSchedIterationAt.set(System.currentTimeMillis());
                if (preHook != null)
                    preHook.call();
                List<VirtualMachineLease> currentLeases = new ArrayList<>();
                leaseBlockingQueue.drainTo(currentLeases);
                final SchedulingResult schedulingResult = taskScheduler.scheduleOnce(taskQueue, currentLeases);
                // mark end of scheduling iteration before assigning tasks.
                taskQueue.getUsageTracker().reset();
                assignTasks(schedulingResult, taskScheduler);
                schedulingResultCallback.call(schedulingResult);
                doPendingActions();
            }
        }
        catch (Exception e) {
            SchedulingResult result = new SchedulingResult(null);
            result.addException(e);
            schedulingResultCallback.call(result);
        }
    }

    private void addPendingRunningTasks() {
        // add any pending running tasks
        if (addRunningTasksQueue.peek() != null) {
            List<Map<String, QueuableTask>> r = new LinkedList<>();
            addRunningTasksQueue.drainTo(r);
            for (Map<String, QueuableTask> m: r) {
                for (Map.Entry<String, QueuableTask> entry: m.entrySet())
                    taskScheduler.getTaskAssignerIntl().call(entry.getValue(), entry.getKey());
            }
        }
    }

    private void removeTasks() {
        if (removeTasksQueue.peek() != null) {
            List<RemoveTaskRequest> l = new LinkedList<>();
            removeTasksQueue.drainTo(l);
            for (RemoveTaskRequest r: l) {
                // remove it from the queue and call taskScheduler to unassign it if hostname is not null
                try {
                    taskQueue.getUsageTracker().removeTask(r.taskId, r.qAttributes);
                } catch (TaskQueueException e1) {
                    // shouldn't happen since we're calling outside of scheduling iteration
                    logger.warn("Unexpected to get exception outside of scheduling iteration: " + e1.getMessage(), e1);
                }
                if (r.hostname != null)
                    taskScheduler.getTaskUnAssigner().call(r.taskId, r.hostname);
            }
        }
    }

    private void setTaskReadyTimes() {
        if (setReadyTimeQueue.peek() != null) {
            List<SetReadyTimeRequest> l = new LinkedList<>();
            setReadyTimeQueue.drainTo(l);
            l.forEach(r -> {
                try {
                    taskQueue.getUsageTracker().setTaskReadyTime(r.taskId, r.qAttributes, r.when);
                } catch (TaskQueueException e) {
                    logger.warn("Unexpected to get exception outside of scheduling iteration: " + e.getMessage(), e);
                }
            });
        }
    }

    private void doPendingActions() {
        final Action1<Map<TaskQueue.TaskState, Collection<QueuableTask>>> action = taskMapRequest.poll();
        try {
            if (action != null)
                action.call(taskQueue.getAllTasks());
        } catch (TaskQueueException e) {
            logger.warn("Unexpected when trying to get task list: " + e.getMessage(), e);
        }
        final Action1<Map<String, Map<VMResource, Double[]>>> rsAction = resStatusRequest.poll();
        try {
            if (rsAction != null)
                rsAction.call(taskScheduler.getResourceStatusIntl());
        } catch (IllegalStateException e) {
            logger.warn("Unexpected when trying to get resource status: " + e.getMessage(), e);
        }
        final Action1<List<VirtualMachineCurrentState>> vmcAction = vmCurrStateRequest.poll();
        try {
            if (vmcAction != null)
                vmcAction.call(taskScheduler.getVmCurrentStatesIntl());
        } catch (IllegalStateException e) {
            logger.warn("Unexpected when trying to get vm current states: " + e.getMessage(), e);
        }
    }

    private boolean doNextIteration() {
        return (System.currentTimeMillis() - lastSchedIterationAt.get()) > maxSchedIterDelay;
    }

    private void assignTasks(SchedulingResult schedulingResult, TaskScheduler taskScheduler) {
        if(!schedulingResult.getResultMap().isEmpty()) {
            for (VMAssignmentResult result: schedulingResult.getResultMap().values()) {
                for (TaskAssignmentResult t: result.getTasksAssigned()) {
                    taskScheduler.getTaskAssignerIntl().call(t.getRequest(), result.getHostname());
                    final List<PreferentialNamedConsumableResourceSet.ConsumeResult> rSets = t.getrSets();
                    if (rSets != null) {
                        final TaskRequest.AssignedResources assignedResources = new TaskRequest.AssignedResources();
                        assignedResources.setConsumedNamedResources(rSets);
                        t.getRequest().setAssignedResources(assignedResources);
                    }
                }
            }
        }
    }

    /**
     * Add new leases to be used for next scheduling iteration. Leases with IDs previously added cannot be added
     * again. If duplicates are found, the scheduling iteration throws an exception and is available via the
     * scheduling result callback. See {@link TaskScheduler#scheduleOnce(List, List)} for details on behavior upon
     * encountering an exception. This method can be called anytime without impacting any currently running scheduling
     * iterations. The leases will be used in the next scheduling iteration.
     * @param leases New leases to use for scheduling.
     */
    public void addLeases(List<? extends VirtualMachineLease> leases) {
        if (leases != null && !leases.isEmpty()) {
            for(VirtualMachineLease l: leases)
                leaseBlockingQueue.offer(l);
        }
    }

    /**
     * Get all of the tasks in the scheduling service's queue and call the given action when it is available. The
     * list of queues returned is in a consistent state, that is, transitionary actions from ongoing scheduling
     * iterations do not affect the returned collection of tasks. Although an ongoing scheduling iteration is
     * unaffected by this call, onset of the next scheduling iteration may be delayed until the call to the given
     * {@code action} returns. Therefore, it is expected that the {@code action} callback return quickly.
     * @param action The action to call with task collection.
     * @throws TaskQueueException if too many actions are pending to get tasks collection.
     */
    public void requestAllTasks(Action1<Map<TaskQueue.TaskState, Collection<QueuableTask>>> action) throws TaskQueueException {
        if (!taskMapRequest.offer(action))
            throw new TaskQueueException("Too many pending actions submitted for getting tasks collection");
    }

    /**
     * Get resource status information and call the given action when available. Although an ongoing scheduling
     * iteration is unaffected by this call, onset of the next scheduling iteration may be delayed until the call to the
     * given {@code action} returns. Therefore, it is expected that the {@code action} callback return quickly.
     * @param action The action to call with resource status.
     * @throws TaskQueueException if too many actions are pending to get resource status.
     */
    public void requestResourceStatus(Action1<Map<String, Map<VMResource, Double[]>>> action) throws TaskQueueException {
        if (!resStatusRequest.offer(action))
            throw new TaskQueueException("Too many pending actions submitted for getting resource status");
    }

    /**
     * Get the current states of all known VMs and call the given action when available. Although an ongoing scheduling
     * iteration is unaffected by this call, onset of the next scheduling iteration may be delayed until the call to the
     * given {@code action} returns. Therefore, it is expected that the {@code action} callback return quickly.
     * @param action The action to call with VM states.
     * @throws TaskQueueException if too many actions are pending to get VM states.
     */
    public void requestVmCurrentStates(Action1<List<VirtualMachineCurrentState>> action) throws TaskQueueException {
        if (!vmCurrStateRequest.offer(action))
            throw new TaskQueueException("Too many pending actions submitted for getting VM current state");
    }

    /**
     * Mark the given tasks as running. This is expected to be called for all tasks that were already running from before
     * {@link com.netflix.fenzo.TaskSchedulingService} started running. For example, when the scheduling service
     * is being started after a restart of the system and there were some tasks launched in the previous run of
     * the system. Any tasks assigned resources during scheduling invoked by this service will be automatically marked
     * as running.
     *

     * @param task The task to mark as running
     * @param hostname The name of the VM that the task is running on.
     */
    public void initializeRunningTask(QueuableTask task, String hostname) {
        addRunningTasksQueue.offer(Collections.singletonMap(hostname, task));
    }

    /**
     * Mark the task to be removed. This is expected to be called for all tasks that were added to the queue, whether or
     * not the task is already running. If the task is running, the hostname parameter must be set, otherwise,
     * it can be null. The actual remove operation is performed before the start of the next scheduling
     * iteration.
     * @param taskId The Id of the task to be removed.
     * @param qAttributes The queue attributes of the queue that the task belongs to
     * @param hostname The name of the VM where the task was assigned resources from, or, null if it was
     *                 not assigned any resources.
     */
    public void removeTask(String taskId, QAttributes qAttributes, String hostname) {
        removeTasksQueue.offer(new RemoveTaskRequest(taskId, qAttributes, hostname));
    }

    /**
     * Set the wall clock time when this task is ready for consideration for resource allocation. Calling this method
     * is safer than directly calling {@link QueuableTask#safeSetReadyAt(long)} since this scheduling service will
     * call the latter when it is safe to do so.
     * @see QueuableTask#getReadyAt()
     * @param taskId The Id of the task.
     * @param attributes The queue attributes of the queue that the task belongs to.
     * @param when The wall clock time in millis when the task is ready for consideration for assignment.
     */
    public void setTaskReadyTime(String taskId, QAttributes attributes, long when) {
        setReadyTimeQueue.offer(new SetReadyTimeRequest(taskId, attributes, when));
    }

    /**
     * Set the getter function that maps a given queuable task object to a list of names of VM groups for which
     * cluster autoscaling rules have been set. This function will be called by autoscaler, if it was setup for
     * the {@link TaskScheduler} using {@link TaskScheduler.Builder#withAutoScaleRule(AutoScaleRule)}, to determine if
     * the autoscaling rule should be triggered for aggressive scale up. The function call is expected to return a list
     * of autoscale group names to which the task can be launched, if there are resources available. If either this
     * function is not set, or if the function returns no entries when called, the task is assumed to be able to run
     * on any autoscale group.
     * @param getter The function that takes a queuable task object and returns a list of autoscale group names
     */
    public void setTaskToClusterAutoScalerMapGetter(Func1<QueuableTask, List<String>> getter) {
        taskToClusterAutoScalerMapGetter = getter;
    }

    public final static class Builder {

        private TaskScheduler taskScheduler = null;
        private Action1<SchedulingResult> schedulingResultCallback = null;
        private InternalTaskQueue taskQueue = null;
        private long loopIntervalMillis = 50;
        private final ScheduledExecutorService executorService;
        private Action0 preHook = null;
        private long maxDelayMillis = 5000L;
        private boolean optimizingShortfallEvaluator = false;

        public Builder() {
            ThreadFactory threadFactory = ThreadFactoryBuilder.newBuilder().withNameFormat("fenzo-main").build();
            executorService = new ScheduledThreadPoolExecutor(1, threadFactory);
        }

        /**
         * Use the given instance of {@link TaskScheduler} for scheduling resources. A task scheduler must be provided
         * before this builder can create the scheduling service.
         * @param taskScheduler The task scheduler instance to use.
         * @return this same {@code Builder}, suitable for further chaining or to build the {@link TaskSchedulingService}.
         */
        public Builder withTaskScheduler(TaskScheduler taskScheduler) {
            this.taskScheduler = taskScheduler;
            return this;
        }

        /**
         * Use the given callback to give scheduling results to at the end of each scheduling iteration. A callback must
         * be provided before this builder can create the scheduling service.
         * @param callback The action to call with scheduling results.
         * @return this same {@code Builder}, suitable for further chaining or to build the {@link TaskSchedulingService}.
         */
        public Builder withSchedulingResultCallback(Action1<SchedulingResult> callback) {
            this.schedulingResultCallback = callback;
            return this;
        }

        /**
         * Use the given instance of {@link com.netflix.fenzo.queues.TaskQueue} from which to get tasks to assign
         * resource to. A task queue must be provided before this builder can create the scheduling service.
         * @param taskQ The task queue from which to get tasks for assignment of resoruces.
         * @return this same {@code Builder}, suitable for further chaining or to build the {@link TaskSchedulingService}.
         */
        public Builder withTaskQueue(TaskQueue taskQ) {
            if (!(taskQ instanceof InternalTaskQueue))
                throw new IllegalArgumentException("Argument is not a valid implementation of task queue");
            taskQueue = (InternalTaskQueue) taskQ;
            return this;
        }

        /**
         * Use the given milli seconds as minimum delay between two consecutive scheduling iterations. Default to 50.
         * @param loopIntervalMillis The delay between consecutive scheduling iterations in milli seconds.
         * @return this same {@code Builder}, suitable for further chaining or to build the {@link TaskSchedulingService}.
         */
        public Builder withLoopIntervalMillis(long loopIntervalMillis) {
            this.loopIntervalMillis = loopIntervalMillis;
            return this;
        }

        /**
         * Use the given milli seconds as the maximum delay between two consecutive scheduling iterations. Default to
         * 5000. Delay between two iterations may be longer than the minimum delay specified using
         * {@link #withLoopIntervalMillis(long)} if the service notices no changes to the queue or there are no new
         * VM leases input.
         * @param maxDelayMillis The maximum delay between two consecutive scheduling iterations.
         * @return this same {@code Builder}, suitable for further chaining or to build the {@link TaskSchedulingService}.
         */
        public Builder withMaxDelayMillis(long maxDelayMillis) {
            this.maxDelayMillis = maxDelayMillis;
            return this;
        }

        /**
         * Use the given action to call before starting a new scheduling iteration. This can be used, for example,
         * to prepare for the next iteration by updating any state that user provided plugins may wish to use for
         * constraints and fitness functions.
         * @param preHook The callback to mark beginning of a new scheduling iteration.
         * @return this same {@code Builder}, suitable for further chaining or to build the {@link TaskSchedulingService}.
         */
        public Builder withPreSchedulingLoopHook(Action0 preHook) {
            this.preHook = preHook;
            return this;
        }

        /**
         * Use an optimal evaluator of resource shortfall for tasks failing assignments to determine the right number
         * of VMs needed for cluster scale up.
         * In a cluster with multiple VM groups, determine the minimum number of VMs of each group required to satisfy
         * the resource demands from pending tasks at the end of a scheduling iteration.
         *

         * The default is to use a naive shortfall evaluator that may scale up more VMs than needed and later correct
         * with an appropriate scale down.
         *

         * Using this evaluator should incur an overhead of approximately one scheduling iteration, only once for
         * the set of unassigned tasks.
         * @return this same {@code Builder}, suitable for further chaining or to build the {@link TaskSchedulingService}.
         */
        public Builder withOptimizingShortfallEvaluator() {
            this.optimizingShortfallEvaluator = true;
            return this;
        }

        /**
         * Creates a {@link TaskSchedulingService} based on the various builder methods you have chained.
         *
         * @return a {@code TaskSchedulingService} built according to the specifications you indicated
         */
        public TaskSchedulingService build() {
            if (taskScheduler == null)
                throw new NullPointerException("Null task scheduler not allowed");
            if (schedulingResultCallback == null)
                throw new NullPointerException("Null scheduling result callback not allowed");
            if (taskQueue == null)
                throw new NullPointerException("Null task queue not allowed");
            final TaskSchedulingService schedulingService = new TaskSchedulingService(this);
            if (optimizingShortfallEvaluator) {
                taskScheduler.getAutoScaler().useOptimizingShortfallAnalyzer();
                taskScheduler.getAutoScaler().setSchedulingService(schedulingService);
            }
            return schedulingService;
        }
    }
}


/*
 * Copyright 2015 Netflix, Inc.
 *
 * 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.netflix.fenzo.triggers;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.quartz.ScheduleBuilder;
import rx.functions.Action1;

import java.util.Date;
import java.util.TimeZone;

import static org.quartz.CronScheduleBuilder.cronSchedule;

/**
 * @warn class description missing
 */
public class CronTrigger<T> extends ScheduledTrigger<T> {
    private String cronExpression;
    private String timeZoneId = "America/Los_Angeles";

    @JsonCreator
    public CronTrigger(@JsonProperty("cronExpression") String cronExpression,
                       @JsonProperty("timeZoneId") String timeZoneId,
                       @JsonProperty("startAt") Date startAt,
                       @JsonProperty("name") String name,
                       @JsonProperty("data") T data,
                       @JsonProperty("dataType") Class<T> dataType,
                       @JsonProperty("action") Class<? extends Action1<T>> action) {
        super(name, data, dataType, action, startAt, null);
        this.cronExpression = cronExpression;
        this.timeZoneId = timeZoneId == null || "".equals(timeZoneId) ? "America/Los_Angeles" : timeZoneId;
        TriggerUtils.validateCronExpression(cronExpression);
    }

    public CronTrigger(String cronExpression,
                       String name,
                       T data,
                       Class<T> dataType,
                       Class<? extends Action1<T>> action) {
        this(cronExpression, "America/Los_Angeles", new Date(), name, data, dataType, action);
    }

    /**
     * @return
     */
    public String getCronExpression() {
        return cronExpression;
    }

    /**
     * @param cronExpression
     */
    public void setCronExpression(String cronExpression) {
        this.cronExpression = cronExpression;
    }

    /**
     * @warn method description missing
     *
     * @return
     */
    @Override
    @JsonIgnore
    public ScheduleBuilder getScheduleBuilder() {
        return cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing().inTimeZone(TimeZone.getTimeZone(timeZoneId));
    }

    /**
     * @return
     */
    public String toString() {
        return "CronTrigger (" + getId() + ":" + getName() + ":" + cronExpression + ")";
    }
}


/*
 * Copyright 2015 Netflix, Inc.
 *
 * 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.netflix.fenzo.samples;

import com.netflix.fenzo.ConstraintEvaluator;
import com.netflix.fenzo.TaskRequest;
import com.netflix.fenzo.VMTaskFitnessCalculator;
import com.netflix.fenzo.functions.Action1;
import com.netflix.fenzo.functions.Func1;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

/**
 * A simple task generator to use with {@link SampleFramework}.
 * This is the main class to run a sample framework.
 * Create and submit a given number of tasks for given number of iterations until all tasks complete.
 */
public class TaskGenerator implements Runnable {

    private final BlockingQueue<TaskRequest> taskQueue;
    private final int numIters;
    private final int numTasks;
    private final AtomicInteger tasksCompleted = new AtomicInteger();

    public TaskGenerator(BlockingQueue<TaskRequest> taskQueue, int numIters, int numTasks) {
        this.taskQueue = taskQueue;
        this.numIters = numIters;
        this.numTasks = numTasks;
    }

    private int launchedTasks = 0;

    @Override
    public void run() {
        for (int i = 0; i < numIters; i++) {
            for (int j = 0; j < numTasks; j++)
                taskQueue.offer(getTaskRequest(launchedTasks++));
            System.out.println("        Generated " + numTasks + " tasks so far");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException ie) {
            }
        }
    }

    private TaskRequest getTaskRequest(final int id) {
        final String taskId = "" + id;
        final AtomicReference<TaskRequest.AssignedResources> assgndResRef = new AtomicReference<>();
        return new TaskRequest() {
            @Override
            public String getId() {
                return taskId;
            }

            @Override
            public String taskGroupName() {
                return "";
            }

            @Override
            public double getCPUs() {
                return 1.0;
            }

            @Override
            public double getMemory() {
                return 1024;
            }

            @Override
            public double getNetworkMbps() {
                return 0;
            }

            @Override
            public double getDisk() {
                return 10;
            }

            @Override
            public int getPorts() {
                return 1;
            }

            @Override
            public Map<String, Double> getScalarRequests() {
                return null;
            }

            @Override
            public List<? extends ConstraintEvaluator> getHardConstraints() {
                return null;
            }

            @Override
            public List<? extends VMTaskFitnessCalculator> getSoftConstraints() {
                return null;
            }

            @Override
            public void setAssignedResources(AssignedResources assignedResources) {
                assgndResRef.set(assignedResources);
            }

            @Override
            public AssignedResources getAssignedResources() {
                return assgndResRef.get();
            }

            @Override
            public Map<String, NamedResourceSetRequest> getCustomNamedResources() {
                return Collections.emptyMap();
            }
        };
    }

    /**
     * Main method to run the task generator.
     * @param args Arguments to the program. Provide as the only argument, the mesos connection string.
     */
    public static void main(String[] args) {
        if(args.length!=1) {
            System.err.println("Must provide one argument - Mesos master location string");
            System.exit(1);
        }
        int numTasks=10;
        int numIters=5;
        BlockingQueue<TaskRequest> taskQueue = new LinkedBlockingQueue<>();
        final TaskGenerator taskGenerator = new TaskGenerator(taskQueue, numIters, numTasks);
        final SampleFramework framework = new SampleFramework(taskQueue, args[0], // mesos master location string
                new Action1<String>() {
                    @Override
                    public void call(String s) {
                        taskGenerator.tasksCompleted.incrementAndGet();
                    }
                },
                new Func1<String, String>() {
                    @Override
                    public String call(String s) {
                        return "sleep 2";
                    }
                });
        long start = System.currentTimeMillis();
        (new Thread(taskGenerator)).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                framework.runAll();
            }
        }).start();
        while(taskGenerator.tasksCompleted.get() < (numIters*numTasks)) {
            System.out.println("NUM TASKS COMPLETED: " + taskGenerator.tasksCompleted.get() + " of " + (numIters*numTasks));
            try{Thread.sleep(1000);}catch(InterruptedException ie){}
        }
        System.out.println("Took " + (System.currentTimeMillis()-start) + " mS to complete " + (numIters*numTasks) + " tasks");
        framework.shutdown();
        System.exit(0);
    }
}


/*
 * Copyright 2015 Netflix, Inc.
 *
 * 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.netflix.fenzo.samples;

import com.netflix.fenzo.SchedulingResult;
import com.netflix.fenzo.TaskAssignmentResult;
import com.netflix.fenzo.TaskRequest;
import com.netflix.fenzo.TaskScheduler;
import com.netflix.fenzo.VMAssignmentResult;
import com.netflix.fenzo.VirtualMachineLease;
import com.netflix.fenzo.functions.Action1;
import com.netflix.fenzo.functions.Func1;
import com.netflix.fenzo.plugins.VMLeaseObject;
import org.apache.mesos.MesosSchedulerDriver;
import org.apache.mesos.Protos;
import org.apache.mesos.Scheduler;
import org.apache.mesos.SchedulerDriver;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

/**
 * A sample Mesos framework that shows a sample usage of Fenzo {@link TaskScheduler}.
 */
public class SampleFramework {

    /**
     * A sample mesos scheduler that shows how mesos callbacks can be setup for use with Fenzo TaskScheduler.
     */
    public class MesosScheduler implements Scheduler {

        /**
         * When we register successfully with mesos, any previous resource offers are invalid. Tell Fenzo scheduler
         * to expire all leases (aka offers) right away.
         */
        @Override
        public void registered(SchedulerDriver driver, Protos.FrameworkID frameworkId, Protos.MasterInfo masterInfo) {
            System.out.println("Registered! ID = " + frameworkId.getValue());
            scheduler.expireAllLeases();
        }

        /**
         * Similar to {@code registered()} method, expire any previously known resource offers by asking Fenzo
         * scheduler to expire all leases right away.
         */
        @Override
        public void reregistered(SchedulerDriver driver, Protos.MasterInfo masterInfo) {
            System.out.println("Re-registered " + masterInfo.getId());
            scheduler.expireAllLeases();
        }

        /**
         * Add the received Mesos resource offers to the lease queue. Fenzo scheduler is used by calling its main
         * allocation routine in a loop, see {@link SampleFramework#runAll()}. Collect offers from mesos into a queue
         * so the next call to Fenzo's allocation routine can pick them up.
         */
        @Override
        public void resourceOffers(SchedulerDriver driver, List<Protos.Offer> offers) {
            for(Protos.Offer offer: offers) {
                System.out.println("Adding offer " + offer.getId() + " from host " + offer.getHostname());
                leasesQueue.offer(new VMLeaseObject(offer));
            }
        }

        /**
         * Tell Fenzo scheduler that a resource offer should be expired immediately.
         */
        @Override
        public void offerRescinded(SchedulerDriver driver, Protos.OfferID offerId) {
            scheduler.expireLease(offerId.getValue());
        }

        /**
         * Update Fenzo scheduler of task completion if received status indicates a terminal state. There is no need
         * to tell Fenzo scheduler of task started because that is supposed to have been already done before launching
         * the task in Mesos.
         *
         * In a real world framework, this state change would also be persisted with a state machine of choice.
         */
        @Override
        public void statusUpdate(SchedulerDriver driver, Protos.TaskStatus status) {
            System.out.println("Task Update: " + status.getTaskId().getValue() + " in state " + status.getState());
            switch (status.getState()) {
                case TASK_FAILED:
                case TASK_LOST:
                case TASK_FINISHED:
                    onTaskComplete.call(status.getTaskId().getValue());
                    scheduler.getTaskUnAssigner().call(status.getTaskId().getValue(), launchedTasks.get(status.getTaskId().getValue()));
                    break;
            }
        }

        @Override
        public void frameworkMessage(SchedulerDriver driver, Protos.ExecutorID executorId, Protos.SlaveID slaveId, byte[] data) {}

        @Override
        public void disconnected(SchedulerDriver driver) {}

        /**
         * Upon slave lost notification, tell Fenzo scheduler to expire all leases with the given slave ID. Note, however,
         * that if there was no offer received from that slave prior to this call, Fenzo would not have a mapping from
         * the slave ID to hostname (Fenzo maintains slaves state by hostname). This is OK since there would be no offers
         * to expire. However, any tasks running on the lost slave will not be removed by this call to Fenzo. Task lost
         * status updates would ensure that.
         */
        @Override
        public void slaveLost(SchedulerDriver driver, Protos.SlaveID slaveId) {
            scheduler.expireAllLeasesByVMId(slaveId.getValue());
        }

        /**
         * Do nothing, instead, rely on task lost status updates to inform Fenzo of task completions.
         */
        @Override
        public void executorLost(SchedulerDriver driver, Protos.ExecutorID executorId, Protos.SlaveID slaveId, int status) {
            System.out.println("Executor " + executorId.getValue() + " lost, status=" + status);
        }

        @Override
        public void error(SchedulerDriver driver, String message) {}
    }

    private final BlockingQueue<TaskRequest> taskQueue;
    private final BlockingQueue<VirtualMachineLease> leasesQueue;
    private final Map<String, String> launchedTasks;
    private final TaskScheduler scheduler;
    private final MesosSchedulerDriver mesosSchedulerDriver;
    private final AtomicReference<MesosSchedulerDriver> ref = new AtomicReference<>();
    private final Map<String, TaskRequest> pendingTasksMap = new HashMap<>();
    private final Action1<String> onTaskComplete;
    private final Func1<String, String> taskCmdGetter;
    private final AtomicBoolean isShutdown = new AtomicBoolean(false);

    /**
     * Create a sample mesos framework with the given task queue and mesos master connection string. As would be typical
     * for frameworks that wish to use Fenzo task scheduler, a lease queue is created for mesos scheduler callback to
     * insert offers received from mesos. This sample implementation obtains the tasks to run via a task queue. The
     * {@link SampleFramework#runAll()} method implements the scheduling loop that continuously takes pending tasks from
     * the queue and uses Fenzo's task scheduler to assign resources to them.
     *
     * The task scheduler created in this sample is a rather simple one, with no advanced features.
     *
     * @param taskQueue The task queue.
     * @param mesosMaster Connection string for mesos master.
     * @param onTaskComplete A single argument action trigger to invoke upon task completion, with task ID is the argument.
     * @param taskCmdGetter A single argument function to invoke to get the command line to execute for a given task ID,
     *                      passed as the only argument.
     */
    public SampleFramework(BlockingQueue<TaskRequest> taskQueue, String mesosMaster, Action1<String> onTaskComplete,
                           Func1<String, String> taskCmdGetter) {
        this.taskQueue = taskQueue;
        this.leasesQueue = new LinkedBlockingQueue<>();
        this.onTaskComplete = onTaskComplete;
        this.taskCmdGetter = taskCmdGetter;
        launchedTasks = new HashMap<>();
        scheduler = new TaskScheduler.Builder()
                .withLeaseOfferExpirySecs(1000000000)
                .withLeaseRejectAction(new Action1<VirtualMachineLease>() {
                    @Override
                    public void call(VirtualMachineLease lease) {
                        System.out.println("Declining offer on " + lease.hostname());
                        ref.get().declineOffer(lease.getOffer().getId());
                    }
                })
                .build();
        Protos.FrameworkInfo framework = Protos.FrameworkInfo.newBuilder()
                .setName("Sample Fenzo Framework")
                .setUser("")
                .build();
        Scheduler mesosScheduler = new MesosScheduler();
        mesosSchedulerDriver = new MesosSchedulerDriver(mesosScheduler, framework, mesosMaster);
        ref.set(mesosSchedulerDriver);
        new Thread() {
            public void run() {
                mesosSchedulerDriver.run();
            }
        }.start();
    }

    /**
     * Shuts down the Mesos driver.
     */
    public void shutdown() {
        System.out.println("Stopping down mesos driver");
        Protos.Status status = mesosSchedulerDriver.stop();
        isShutdown.set(true);
    }

    public void start() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                runAll();
            }
        }).start();
    }

    /**
     * Run scheduling loop until shutdown is called.
     * This sample implementation shows the general pattern of using Fenzo's task scheduler. Scheduling occurs in a
     * continuous loop, iteratively calling {@link TaskScheduler#scheduleOnce(List, List)} method, passing in the
     * list of tasks currently pending launch, and a list of any new resource offers obtained from mesos since the last
     * time the lease queue was drained. The call returns an assignments result object from which any tasks assigned
     * can be launched via the mesos driver. The result contains a map with hostname as the key and assignment result
     * as the value. The assignment result contains the resource offers of the host that were used for the assignments
     * and a list of tasks assigned resources from the host. The resource offers were removed from the internal state in
     * Fenzo. However, the task assignments are not updated, yet. If the returned assignments are being used to launch
     * the tasks in mesos, call Fenzo's task assigner for each task launched to indicate that the task is being launched.
     * If the returned assignments are not being used, the resource offers in the assignment results must either be
     * rejected in mesos, or added back into Fenzo explicitly.
     */
    void runAll() {
        System.out.println("Running all");
        List<VirtualMachineLease> newLeases = new ArrayList<>();
        while(true) {
            if(isShutdown.get())
                return;
            newLeases.clear();
            List<TaskRequest> newTaskRequests = new ArrayList<>();
            System.out.println("#Pending tasks: " + pendingTasksMap.size());
            TaskRequest taskRequest=null;
            try {
                taskRequest = pendingTasksMap.size()==0?
                        taskQueue.poll(5, TimeUnit.SECONDS) :
                        taskQueue.poll(1, TimeUnit.MILLISECONDS);
            }
            catch (InterruptedException ie) {
                System.err.println("Error polling task queue: " + ie.getMessage());
            }
            if(taskRequest!=null) {
                taskQueue.drainTo(newTaskRequests);
                newTaskRequests.add(0, taskRequest);
                for(TaskRequest request: newTaskRequests)
                    pendingTasksMap.put(request.getId(), request);
            }
            leasesQueue.drainTo(newLeases);
            SchedulingResult schedulingResult = scheduler.scheduleOnce(new ArrayList<>(pendingTasksMap.values()), newLeases);
            System.out.println("result=" + schedulingResult);
            Map<String,VMAssignmentResult> resultMap = schedulingResult.getResultMap();
            if(!resultMap.isEmpty()) {
                for(VMAssignmentResult result: resultMap.values()) {
                    List<VirtualMachineLease> leasesUsed = result.getLeasesUsed();
                    List<Protos.TaskInfo> taskInfos = new ArrayList<>();
                    StringBuilder stringBuilder = new StringBuilder("Launching on VM " + leasesUsed.get(0).hostname() + " tasks ");
                    final Protos.SlaveID slaveId = leasesUsed.get(0).getOffer().getSlaveId();
                    for(TaskAssignmentResult t: result.getTasksAssigned()) {
                        stringBuilder.append(t.getTaskId()).append(", ");
                        taskInfos.add(getTaskInfo(slaveId, t.getTaskId(), taskCmdGetter.call(t.getTaskId())));
                        // unqueueTask task from pending tasks map and put into launched tasks map
                        // (in real world, transition the task state)
                        pendingTasksMap.remove(t.getTaskId());
                        launchedTasks.put(t.getTaskId(), leasesUsed.get(0).hostname());
                        scheduler.getTaskAssigner().call(t.getRequest(), leasesUsed.get(0).hostname());
                    }
                    List<Protos.OfferID> offerIDs = new ArrayList<>();
                    for(VirtualMachineLease l: leasesUsed)
                        offerIDs.add(l.getOffer().getId());
                    System.out.println(stringBuilder.toString());
                    mesosSchedulerDriver.launchTasks(offerIDs, taskInfos);
                }
            }
            // insert a short delay before scheduling any new tasks or tasks from before that haven't been launched yet.
            try{Thread.sleep(100);}catch(InterruptedException ie){}
        }
    }

    static Protos.TaskInfo getTaskInfo(Protos.SlaveID slaveID, final String taskId, String cmd) {
        Protos.TaskID pTaskId = Protos.TaskID.newBuilder()
                .setValue(taskId).build();
        return Protos.TaskInfo.newBuilder()
                .setName("task " + pTaskId.getValue())
                .setTaskId(pTaskId)
                .setSlaveId(slaveID)
                .addResources(Protos.Resource.newBuilder()
                        .setName("cpus")
                        .setType(Protos.Value.Type.SCALAR)
                        .setScalar(Protos.Value.Scalar.newBuilder().setValue(1)))
                .addResources(Protos.Resource.newBuilder()
                        .setName("mem")
                        .setType(Protos.Value.Type.SCALAR)
                        .setScalar(Protos.Value.Scalar.newBuilder().setValue(128)))
                .setCommand(Protos.CommandInfo.newBuilder().setValue(cmd).build())
                .build();
    }

}