text: package de.metas.vertical.pharma.msv3.server.security;
  
  import java.util.List;
  import java.util.Objects;
  import java.util.concurrent.atomic.AtomicInteger;
  
  import javax.transaction.Transactional;
  
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import org.springframework.beans.factory.annotation.Value;
  import org.springframework.security.core.Authentication;
  import org.springframework.security.core.context.SecurityContextHolder;
  import org.springframework.security.core.userdetails.UserDetailsService;
  import org.springframework.security.core.userdetails.UsernameNotFoundException;
  import org.springframework.stereotype.Component;
  
  import com.google.common.collect.ImmutableList;
  
  import de.metas.vertical.pharma.msv3.protocol.types.BPartnerId;
  import de.metas.vertical.pharma.msv3.protocol.types.ClientSoftwareId;
  import de.metas.vertical.pharma.msv3.server.peer.protocol.MSV3MetasfreshUserId;
  import de.metas.vertical.pharma.msv3.server.peer.protocol.MSV3UserChangedBatchEvent;
  import de.metas.vertical.pharma.msv3.server.peer.protocol.MSV3UserChangedEvent;
  import de.metas.vertical.pharma.msv3.server.peer.protocol.MSV3UserChangedEvent.ChangeType;
  import de.metas.vertical.pharma.msv3.server.security.jpa.JpaUser;
  import de.metas.vertical.pharma.msv3.server.security.jpa.JpaUserRepository;
  import lombok.NonNull;
  
  /*
   * #%L
   * metasfresh-pharma.msv3.server
   * %%
   * Copyright (C) 2018 metas GmbH
   * %%
   * This program is free software: you can redistribute it and/or modify
   * it under the terms of the GNU General Public License as
   * published by the Free Software Foundation, either version 2 of the
   * License, or (at your option) any later version.
   *
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   * GNU General Public License for more details.
   *
   * You should have received a copy of the GNU General Public
   * License along with this program. If not, see
   * <http://www.gnu.org/licenses/gpl-2.0.html>.
   * #L%
   */
  
  @Component
  public class MSV3ServerAuthenticationService implements UserDetailsService
  {
  	private static final Logger logger = LoggerFactory.getLogger(MSV3ServerAuthenticationService.class);
  
  	private final JpaUserRepository usersRepo;
  
  	private final MSV3User adminUser;
  
  	public MSV3ServerAuthenticationService(
  			@NonNull final JpaUserRepository usersRepo,
  			@Value("${msv3server.admin.username:}") final String serverAdminUsername,
  			@Value("${msv3server.admin.password:}") final String serverAdminPassword)
  	{
  		this.usersRepo = usersRepo;
  
  		if (serverAdminUsername != null && serverAdminPassword != null)
  		{
  			adminUser = MSV3User.builder()
  					.username(serverAdminUsername)
  					.password(serverAdminPassword)
  					.serverAdmin(true)
  					.build();
  		}
  		else
  		{
  			adminUser = null;
  		}
  		logger.info("Server admin user: {}", adminUser);
  	}
  
  	@Override
  	public MSV3User loadUserByUsername(final String username) throws UsernameNotFoundException
  	{
  		if (adminUser != null && Objects.equals(username, adminUser.getUsername()))
  		{
  			return adminUser;
  		}
  
  		final JpaUser jpaUser = usersRepo.findByMfUsername(username);
  		if (jpaUser == null || jpaUser.isDeleted())
  		{
  			throw new UsernameNotFoundException("User '" + username + "' does not exist");
  		}
  
  		return toMSV3User(jpaUser);
  	}
  
  	private static MSV3User toMSV3User(@NonNull final JpaUser jpaUser)
  	{
  		return MSV3User.builder()
  				.metasfreshMSV3UserId(MSV3MetasfreshUserId.of(jpaUser.getMfMSV3UserId()))
  				.username(jpaUser.getMfUsername())
  				.password(jpaUser.getMfPassword())
  				.bpartnerId(BPartnerId.of(jpaUser.getMfBpartnerId(), jpaUser.getMfBpartnerLocationId()))
  				.build();
  	}
  
  	@Deprecated
  	public void assertValidClientSoftwareId(final String clientSoftwareId)
  	{
  		// TODO implement
  	}
  
  	public void assertValidClientSoftwareId(final ClientSoftwareId clientSoftwareId)
  	{
  		// TODO implement
  	}
  
  	public BPartnerId getCurrentBPartner()
  	{
  		return getCurrentUser().getBpartnerId();
  	}
  
  	public MSV3User getCurrentUser()
  	{
  		final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
  		if (authentication == null)
  		{
  			throw new IllegalStateException("No authentication found");
  		}
  
  		return loadUserByUsername(authentication.getName());
  	}
  
  	public List<MSV3User> getAllUsers()
  	{
  		return usersRepo.findAll()
  				.stream()
  				.map(jpaUser -> toMSV3User(jpaUser))
  				.collect(ImmutableList.toImmutableList());
  	}
  
  	@Transactional
  	public void handleEvent(@NonNull final MSV3UserChangedBatchEvent batchEvent)
  	{
  		final String mfSyncToken = batchEvent.getId();
  
  		//
  		// Update/Delete
  		{
  			final AtomicInteger countUpdated = new AtomicInteger();
  
  			for (final MSV3UserChangedEvent event : batchEvent.getEvents())
  			{
  				handleEvent(event, mfSyncToken);
  				countUpdated.incrementAndGet();
  			}
  			logger.debug("Updated/Deleted {} users", countUpdated);
  		}
  
  		//
  		// Delete
  		if (batchEvent.isDeleteAllOtherUsers())
  		{
  			final long countDeleted = usersRepo.deleteInBatchByMfSyncTokenNot(mfSyncToken);
  			logger.debug("Deleted {} users", countDeleted);
  		}
  	}
  
  	private void handleEvent(@NonNull final MSV3UserChangedEvent event, final String mfSyncToken)
  	{
  		if (event.getChangeType() == ChangeType.CREATED_OR_UPDATED)
  		{
  			final String username = event.getUsername();
  
  			JpaUser user = usersRepo.findByMfMSV3UserId(event.getMsv3MetasfreshUserId().getId());
  			if (user == null)
  			{
  				user = new JpaUser();
  				user.setMfMSV3UserId(event.getMsv3MetasfreshUserId().getId());
  			}
  
  			user.setMfUsername(username);
  			user.setMfPassword(event.getPassword());
  			user.setMfBpartnerId(event.getBpartnerId());
  			user.setMfBpartnerLocationId(event.getBpartnerLocationId());
  			user.setMfSyncToken(mfSyncToken);
  			usersRepo.save(user);
  		}
  		else if (event.getChangeType() == ChangeType.DELETED)
  		{
  			usersRepo.deleteByMfMSV3UserId(event.getMsv3MetasfreshUserId().getId());
  		}
  		else
  		{
  			throw new IllegalArgumentException("Unknown change type: " + event);
  		}
  	}
  }
  