<commit_before>//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// This file is a part of the 'esoco-business' project.
// Copyright 2016 <NAME>, esoco GmbH, Flensburg, Germany
//
// 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 de.esoco.process.step;

import de.esoco.data.DataRelationTypes;
import de.esoco.data.SessionData;
import de.esoco.data.SessionManager;
import de.esoco.data.element.StringDataElement;

import de.esoco.lib.property.Alignment;
import de.esoco.lib.property.ContentType;
import de.esoco.lib.property.Layout;

import de.esoco.process.Parameter;
import de.esoco.process.ParameterEventHandler;

import org.obrel.type.MetaTypes;

import static de.esoco.lib.property.StateProperties.FOCUSED;

import static de.esoco.process.ProcessRelationTypes.PROCESS_USER;


/********************************************************************
 * A generic login fragment for the authentication process of an application.
 *
 * @author eso
 */
public class LoginFragment extends InteractionFragment
{
	//~ Enums ------------------------------------------------------------------

	/********************************************************************
	 * Enumeration of the available login actions.
	 */
	public enum LoginAction { LOGIN, REGISTER, RESET_PASSWORD }

	//~ Static fields/initializers ---------------------------------------------

	private static final long serialVersionUID = 1L;

	//~ Instance fields --------------------------------------------------------

	private Parameter<String> aLoginName;
	private Parameter<String> aPassword;
	private Parameter<String> aErrorMessage;

	//~ Methods ----------------------------------------------------------------

	/***************************************
	 * {@inheritDoc}
	 */
	@Override
	public void init() throws Exception
	{
		layout(Layout.FORM);

		aLoginName =
			inputText("LoginName").ensureNotEmpty().set(FOCUSED)
								  .onAction(new ParameterEventHandler<String>()
				{
					@Override
					public void handleParameterUpdate(String sLoginName)
						throws Exception
					{
						handleLoginNameInput(sLoginName);
					}
				});
		aPassword  =
			inputText("Password").content(ContentType.PASSWORD).ensureNotEmpty()
								 .onAction(new ParameterEventHandler<String>()
				{
					@Override
					public void handleParameterUpdate(String sPassword)
						throws Exception
					{
						handlePasswordInput(sPassword);
					}
				});

		aErrorMessage = label("").style("AuthenticationError").hide();

		buttons(LoginAction.LOGIN).alignHorizontal(Alignment.CENTER)
								  .continueOnInteraction(false)
								  .onAction(new ParameterEventHandler<LoginAction>()
			{
				@Override
				public void handleParameterUpdate(LoginAction eAction)
					throws Exception
				{
					handleLoginAction(eAction);
				}
			});
	}

	/***************************************
	 * Handles the login action that occurred.
	 *
	 * @param eAction The login action
	 */
	private void handleLoginAction(LoginAction eAction)
	{
		String sLoginName = aLoginName.value();
		String sPassword  = aPassword.value();

		StringDataElement aLoginData =
			new StringDataElement(sLoginName, sPassword);

		try
		{
			SessionManager rSessionManager =
				getParameter(DataRelationTypes.SESSION_MANAGER);

			rSessionManager.loginUser(aLoginData);

			// update the process user to use the authenticated person
			setParameter(PROCESS_USER,
						 rSessionManager.getSessionData()
						 .get(SessionData.SESSION_USER));

			aPassword.value("");
			setParameter(MetaTypes.AUTHENTICATED, true);
			continueOnInteraction(getInteractiveInputParameter());
		}
		catch (Exception e)
		{
			aErrorMessage.show().value("$msgLoginError");
		}
	}

	/***************************************
	 * Handles login name input.
	 *
	 * @param sLoginName The login name value
	 */
	private void handleLoginNameInput(String sLoginName)
	{
		if (aPassword.value().length() > 5)
		{
			handleLoginAction(LoginAction.LOGIN);
		}
		else
		{
			aPassword.set(FOCUSED);
		}
	}

	/***************************************
	 * Handles password input.
	 *
	 * @param sPassword The password
	 */
	private void handlePasswordInput(String sPassword)
	{
		handleLoginAction(LoginAction.LOGIN);
	}
}<commit_msg>FRAMEWORK-205 (Improvement): LoginFragment: add delay after unsuccessful login attempts<commit_after>//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// This file is a part of the 'esoco-business' project.
// Copyright 2016 <NAME>, esoco GmbH, Flensburg, Germany
//
// 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 de.esoco.process.step;

import de.esoco.data.DataRelationTypes;
import de.esoco.data.SessionData;
import de.esoco.data.SessionManager;
import de.esoco.data.element.StringDataElement;

import de.esoco.lib.property.Alignment;
import de.esoco.lib.property.ContentType;
import de.esoco.lib.property.Layout;

import de.esoco.process.Parameter;
import de.esoco.process.ParameterEventHandler;
import de.esoco.process.ProcessRelationTypes;

import org.obrel.type.MetaTypes;

import static de.esoco.lib.property.StateProperties.FOCUSED;

import static de.esoco.process.ProcessRelationTypes.PROCESS_USER;


/********************************************************************
 * A generic login fragment for the authentication process of an application.
 *
 * @author eso
 */
public class LoginFragment extends InteractionFragment
{
	//~ Enums ------------------------------------------------------------------

	/********************************************************************
	 * Enumeration of the available login actions.
	 */
	public enum LoginAction { LOGIN, REGISTER, RESET_PASSWORD }

	//~ Static fields/initializers ---------------------------------------------

	private static final long serialVersionUID = 1L;

	//~ Instance fields --------------------------------------------------------

	private final int nMaxLoginAttempts;
	private final int nInitialLoginErrorWaitTime;

	private Parameter<String> aLoginName;
	private Parameter<String> aPassword;
	private Parameter<String> aErrorMessage;

	private int  nErrorCount     = 0;
	private int  nErrorWaitTime  = 0;
	private long nErrorWaitStart;

	//~ Constructors -----------------------------------------------------------

	/***************************************
	 * Default constructor with 3 allowed login attempts and 5 seconds initial
	 * wait time.
	 */
	public LoginFragment()
	{
		this(3, 5);
	}

	/***************************************
	 * Creates a new instance with the given login failure parameters.
	 *
	 * @param nMaxLoginAttemptsUntilDelay The maximum number of login attempts
	 *                                    that can be done until the user must
	 *                                    wait a certain time before the next
	 *                                    attempt. -1 disabled login attempt
	 *                                    delays completely.
	 * @param nInitialLoginErrorWaitTime  The initial time in seconds the user
	 *                                    must wait after the maximum number of
	 *                                    login attempts has been reached; this
	 *                                    will double with each failure cycle.
	 */
	public LoginFragment(
		int nMaxLoginAttemptsUntilDelay,
		int nInitialLoginErrorWaitTime)
	{
		this.nMaxLoginAttempts		    = nMaxLoginAttemptsUntilDelay;
		this.nInitialLoginErrorWaitTime = nInitialLoginErrorWaitTime;
	}

	//~ Methods ----------------------------------------------------------------

	/***************************************
	 * {@inheritDoc}
	 */
	@Override
	public void init() throws Exception
	{
		layout(Layout.FORM);

		aLoginName =
			inputText("LoginName").ensureNotEmpty().set(FOCUSED)
								  .onAction(new ParameterEventHandler<String>()
				{
					@Override
					public void handleParameterUpdate(String sLoginName)
						throws Exception
					{
						handleLoginNameInput(sLoginName);
					}
				});
		aPassword  =
			inputText("Password").content(ContentType.PASSWORD).ensureNotEmpty()
								 .onAction(new ParameterEventHandler<String>()
				{
					@Override
					public void handleParameterUpdate(String sPassword)
						throws Exception
					{
						handlePasswordInput(sPassword);
					}
				});

		aErrorMessage = label("").style("AuthenticationError").hide();

		buttons(LoginAction.LOGIN).alignHorizontal(Alignment.CENTER)
								  .continueOnInteraction(false)
								  .onAction(new ParameterEventHandler<LoginAction>()
			{
				@Override
				public void handleParameterUpdate(LoginAction eAction)
					throws Exception
				{
					performLogin();
				}
			});
	}

	/***************************************
	 * Adds the next domain availability check result to this fragment on an
	 * auto-update interaction.
	 *
	 * @see InteractionFragment#prepareInteraction()
	 */
	@Override
	@SuppressWarnings("boxing")
	public void prepareInteraction() throws Exception
	{
		if (nErrorWaitTime > 0)
		{
			int nWaitSeconds =
				(int) ((System.currentTimeMillis() - nErrorWaitStart) / 1000);

			if (nWaitSeconds > nErrorWaitTime)
			{
				aErrorMessage.hide().value("");
				fragmentParam().enableEdit(true);
				set(ProcessRelationTypes.AUTO_UPDATE, false);
			}
			else
			{
				aErrorMessage.value(createErrorWaitMessage(nErrorWaitTime -
														   nWaitSeconds));
				Thread.sleep(1000);
			}
		}
	}

	/***************************************
	 * Creates the waiting message after successive login failures.
	 *
	 * @param  nRemainingSeconds The number of seconds the user has to wait
	 *
	 * @return
	 */
	@SuppressWarnings("boxing")
	private String createErrorWaitMessage(int nRemainingSeconds)
	{
		return String.format("$${$msgSuccessiveLoginErrorStart} %d " +
							 "{$msgSuccessiveLoginErrorEnd}",
							 nRemainingSeconds);
	}

	/***************************************
	 * Handles login name input.
	 *
	 * @param sLoginName The login name value
	 */
	private void handleLoginNameInput(String sLoginName)
	{
		if (aPassword.value().length() > 5)
		{
			performLogin();
		}
		else
		{
			aPassword.set(FOCUSED);
		}
	}

	/***************************************
	 * Handles password input.
	 *
	 * @param sPassword The password
	 */
	private void handlePasswordInput(String sPassword)
	{
		performLogin();
	}

	/***************************************
	 * Handles the login action that occurred.
	 */
	private void performLogin()
	{
		String sLoginName = aLoginName.value();
		String sPassword  = aPassword.value();

		StringDataElement aLoginData =
			new StringDataElement(sLoginName, sPassword);

		try
		{
			SessionManager rSessionManager =
				getParameter(DataRelationTypes.SESSION_MANAGER);

			rSessionManager.loginUser(aLoginData);

			nErrorCount    = 0;
			nErrorWaitTime = 0;

			// update the process user to use the authenticated person
			setParameter(PROCESS_USER,
						 rSessionManager.getSessionData()
						 .get(SessionData.SESSION_USER));

			aPassword.value("");
			setParameter(MetaTypes.AUTHENTICATED, true);
			continueOnInteraction(getInteractiveInputParameter());
		}
		catch (Exception e)
		{
			String sMessage = "$msgLoginError";

			if (nMaxLoginAttempts > 0 && ++nErrorCount >= nMaxLoginAttempts)
			{
				nErrorWaitTime =
					nErrorWaitTime == 0 ? nInitialLoginErrorWaitTime
										: nErrorWaitTime * 2;

				nErrorCount     = 0;
				nErrorWaitStart = System.currentTimeMillis();
				sMessage	    = createErrorWaitMessage(nErrorWaitTime);

				fragmentParam().enableEdit(false);
				set(ProcessRelationTypes.AUTO_UPDATE);
			}

			aErrorMessage.show().value(sMessage);
		}
	}
}
{'fraction_non_alphanumeric': 0.20477789054571005, 'fraction_numerical': 0.0035131548130220938, 'mean_word_length': 3.0900383141762453, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 11, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '252013', 'n_tokens_mistral': 3959, 'n_tokens_neox': 3266, 'n_words': 824}