All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.ligoj.app.plugin.id.resource.AbstractContainerResource Maven / Gradle / Ivy

There is a newer version: 2.2.5
Show newest version
/*
 * Licensed under MIT (https://github.com/ligoj/ligoj/blob/master/LICENSE)
 */
package org.ligoj.app.plugin.id.resource;

import jakarta.ws.rs.*;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.UriInfo;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.ligoj.app.api.Normalizer;
import org.ligoj.app.iam.ContainerOrg;
import org.ligoj.app.iam.IContainerRepository;
import org.ligoj.app.iam.dao.CacheContainerRepository;
import org.ligoj.app.iam.dao.DelegateOrgRepository;
import org.ligoj.app.iam.model.CacheContainer;
import org.ligoj.app.model.ContainerType;
import org.ligoj.app.plugin.id.DnUtils;
import org.ligoj.app.plugin.id.model.ContainerScope;
import org.ligoj.bootstrap.core.NamedBean;
import org.ligoj.bootstrap.core.json.PaginationJson;
import org.ligoj.bootstrap.core.json.TableItem;
import org.ligoj.bootstrap.core.json.datatable.DataTableAttributes;
import org.ligoj.bootstrap.core.resource.BusinessException;
import org.ligoj.bootstrap.core.resource.OnNullReturn404;
import org.ligoj.bootstrap.core.validation.ValidationJsonException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * Basic container operations.
 *
 * @param  The container type.
 * @param  The container edition bean type.
 * @param  The container cache type.
 */
@Slf4j
public abstract class AbstractContainerResource
		extends AbstractOrgResource {

	protected static final String TYPE_ATTRIBUTE = "type";

	/**
	 * Ordered columns.
	 */
	protected static final Map ORDERED_COLUMNS = new HashMap<>();

	static {
		ORDERED_COLUMNS.put("name", "name");
	}

	@Autowired
	protected ContainerScopeResource containerScopeResource;

	@Autowired
	protected PaginationJson paginationJson;

	@Autowired
	protected DelegateOrgRepository delegateRepository;

	/**
	 * The container type manager by this instance.
	 */
	protected final ContainerType type;

	/**
	 * Order {@link ContainerScope} by container type.
	 */
	@AllArgsConstructor
	public class TypeComparator implements Comparator {

		/**
		 * Container types
		 */
		private List types;

		@Override
		public int compare(final T container1, final T container2) {
			final int result;

			// First compare the type
			final var type1 = toScope(types, container1);
			final var type2 = toScope(types, container2);
			if (Objects.equals(type1, type2)) {
				result = 0;
			} else if (type1 == null) {
				result = 1;
			} else if (type2 == null) {
				result = -1;
			} else {
				result = type1.getName().compareToIgnoreCase(type2.getName());
			}

			// Then the compare the container name
			if (result == 0) {
				return container1.getName().compareToIgnoreCase(container2.getName());
			}
			return result;
		}
	}

	/**
	 * Constructor with mandatory fields.
	 *
	 * @param type The container type.
	 */
	protected AbstractContainerResource(final ContainerType type) {
		this.type = type;
	}

	/**
	 * Check the container can be deleted by the current user.
	 *
	 * @param container The container to delete.
	 */
	protected void checkForDeletion(final ContainerOrg container) {

		// Check the container can be deleted by the current user. Used DN will
		// be FQN to match the delegates
		if (!delegateRepository.canCreate(securityHelper.getLogin(), Normalizer.normalize(container.getDn()),
				this.type.getDelegateType())) {
			// Not managed container, report this attempt and act as if this
			// company did not exist
			log.warn("Attempt to delete a {} '{}' out of scope", type, container.getName());
			throw new ValidationJsonException(getTypeName(), BusinessException.KEY_UNKNOWN_ID, "0", getTypeName(), "1",
					container.getId());
		}

		// Check this container is not locked
		if (container.isLocked()) {
			throw new ValidationJsonException("company", "locked", "0", container.getName());
		}
	}

	/**
	 * Create the given container.
* The delegation system is involved for this operation and requires administration privilege on the parent tree or * group/company. * * @param container The container to create. * @return The identifier of created {@link org.ligoj.app.iam.ContainerOrg}. */ @POST public String create(final V container) { return createInternal(container).getId(); } protected T create(final V container, final ContainerScope type, final String newDn) { log.info("Creating a {}@{}-{} '{}'", this.type, type.getName(), type.getId(), container.getName()); return getRepository().create(newDn, container.getName()); } /** * Create the given container.
* The delegation system is involved for this operation and requires administration privilege on the parent tree or * group/company.
* Note this is for internal use since the returned object corresponds to the internal representation. * * @param container The container to create. * @return The created {@link org.ligoj.app.iam.ContainerOrg} internal identifier. */ public T createInternal(final V container) { // Check the unlocked scope exists final var scope = containerScopeResource.findById(container.getScope()); // Check the type matches with this class' container type if (this.type != scope.getType()) { throw new ValidationJsonException(TYPE_ATTRIBUTE, "container-scope-match", TYPE_ATTRIBUTE, this.type, "provided", scope.getType()); } // Build the new DN, keeping the case final var newDn = toDn(container, scope); // Check the container can be created by the current principal. // Used DN will be FQN to match the delegates if (!delegateRepository.canCreate(securityHelper.getLogin(), Normalizer.normalize(newDn), this.type.getDelegateType())) { // Not managed container, report this attempt and act as if this // container already exists log.warn("Attempt to create a {} '{}' out of scope", scope, container.getName()); throw new ValidationJsonException("name", "read-only", "0", getTypeName(), "1", container.getName()); } // Check the container does not exist if (getRepository().findById(Normalizer.normalize(container.getName())) != null) { throw new ValidationJsonException("name", "already-exist", "0", getTypeName(), "1", container.getName()); } // Create the new container return create(container, scope, newDn); } /** * Delete an existing container.
* The delegation system is involved for this operation and requires administration privilege on this container. * * @param id The container's identifier. */ @DELETE @Path("{id}") public void delete(@PathParam("id") final String id) { // Check the container exists final var container = findByIdExpected(id); // Check the container can be deleted by current user checkForDeletion(container); // Perform the deletion when checked getRepository().delete(container); } /** * Find a container from its identifier. * * @param id The container's identifier. Will be normalized. * @return The container from its identifier. null if the container is not found or cannot be seen by * the current user */ public T findById(final String id) { return getRepository().findById(securityHelper.getLogin(), id); } /** * Find a container from its identifier. If the container is not found or cannot be seen by the current user, the * error code {@link org.ligoj.bootstrap.core.resource.BusinessException#KEY_UNKNOWN_ID} will be returned. * * @param id The container's identifier. Will be normalized. * @return The container from its identifier. */ public T findByIdExpected(final String id) { return getRepository().findByIdExpected(securityHelper.getLogin(), id); } /** * Return the container matching to given name. Case is sensitive. Visibility is checked against security context. * DN is not exposed. * * @param name the container name. Exact match is required, so a normalized version. * @return Container (CN) with its type. */ @GET @Path("{container:" + ContainerOrg.NAME_PATTERN + "}") @OnNullReturn404 public ContainerWithScopeVo findByName(@PathParam("container") final String name) { return Optional.ofNullable(findById(name)).map(this::toVo).orElse(null); } /** * Return the repository managing the container as cache. * * @return the repository managing the container as cache. */ protected abstract CacheContainerRepository getCacheRepository(); /** * Return containers the current user can see. * * @return ordered containers the current user can see. */ public Set getContainers() { return toInternal(getCacheRepository().findAll(securityHelper.getLogin())); } /** * Return containers the current user can see. * * @param criteria Optional criteria, can be null. * @param pageRequest Optional {@link Pageable}, can be null. * @return ordered containers the current user can see. */ public Page getContainers(final String criteria, final Pageable pageRequest) { return toInternal(getCacheRepository().findAll(securityHelper.getLogin(), criteria, pageRequest)); } /** * Return containers the current user can see. A user always sees his company, as if he had a company delegation to * see it. * * @param uriInfo filter data. * @return containers the current user can see. */ @GET @Path("filter/read") public TableItem getContainers(@Context final UriInfo uriInfo) { return paginationJson.applyPagination(uriInfo, getCacheRepository().findAll(securityHelper.getLogin(), DataTableAttributes.getSearch(uriInfo), paginationJson.getPageRequest(uriInfo, ORDERED_COLUMNS)), CacheContainer::getName); } /** * Return containers the given user can manage with administration access. * * @return ordered companies the given user can manage with administration access. */ protected Set getContainersForAdmin() { return toInternal(getCacheRepository().findAllAdmin(securityHelper.getLogin())); } /** * Return containers' identifier the given user can manage with administration access. * * @return ordered containers the given user can manage with write access. */ public Set getContainersIdForAdmin() { return getContainersIdForX(getCacheRepository()::findAllAdmin); } /** * Return containers' identifier the current user can manage with provider access. * * @return ordered containers the given user can manage with write access. */ private Set getContainersIdForX(Function> dataProvider) { final var all = getRepository().findAll(); return dataProvider.apply(securityHelper.getLogin()).stream().map(CacheContainer::getId).filter(all::containsKey) .collect(Collectors.toSet()); } /** * Return containers the current user can manage with administration access. * * @param uriInfo filter data. * @return containers the current user can manage. */ @GET @Path("filter/admin") public TableItem getContainersForAdmin(@Context final UriInfo uriInfo) { return paginationJson.applyPagination(uriInfo, getCacheRepository().findAllAdmin(securityHelper.getLogin(), DataTableAttributes.getSearch(uriInfo), paginationJson.getPageRequest(uriInfo, ORDERED_COLUMNS)), CacheContainer::getName); } /** * Return containers the given user can manage with write access. * * @return ordered containers the given user can manage with write access. */ public Set getContainersForWrite() { return toInternal(getCacheRepository().findAllWrite(securityHelper.getLogin())); } /** * Return containers' identifier the given user can manage with write access. * * @return ordered containers the given user can manage with write access. */ public Set getContainersIdForWrite() { return getContainersIdForX(getCacheRepository()::findAllWrite); } /** * Return containers the current user can manage with write access. * * @param uriInfo filter data. * @return containers the current user can manage. */ @GET @Path("filter/write") public TableItem getContainersForWrite(@Context final UriInfo uriInfo) { return paginationJson.applyPagination(uriInfo, getCacheRepository().findAllWrite(securityHelper.getLogin(), DataTableAttributes.getSearch(uriInfo), paginationJson.getPageRequest(uriInfo, ORDERED_COLUMNS)), CacheContainer::getName); } /** * Return the repository managing the container. * * @return the repository managing the container. */ protected abstract IContainerRepository getRepository(); protected String getTypeName() { return getRepository().getTypeName(); } /** * Build a new secured container managing the effective visibility and rights. * * @param rawContainer the raw container contained sensitive data. * @param canWrite The containers the principal user can write. * @param canAdmin The containers the principal user can administer. * @param types The defined type with locking information. * @param securedContainer The container count object to fill. */ protected void fillContainerCountVo(final T rawContainer, final Set canWrite, final Set canAdmin, final List types, final ContainerCountVo securedContainer, final Map all) { NamedBean.copy(rawContainer, securedContainer); securedContainer.setCanWrite(canWrite.contains(rawContainer.getId())); securedContainer.setCanAdmin(canAdmin.contains(rawContainer.getId())); securedContainer.setContainerType(type); // Find the closest type final var scope = toScope(types, rawContainer); if (scope != null) { securedContainer.setScope(scope.getName()); securedContainer.setLocked(scope.isLocked()); } securedContainer.setLocked(securedContainer.isLocked() || rawContainer.isLocked()); final var parent = rawContainer.getParent(); if (parent != null) { securedContainer.setParents(new ArrayList<>()); fillParents(securedContainer.getParents(), rawContainer, all); } } private void fillParents(final List target, final T currentGroup, final Map all) { if (currentGroup.getParent() != null) { target.add(currentGroup.getParent()); fillParents(target, all.get(currentGroup.getParent()), all); } } /** * Return the DN from the container and the computed scope. * * @param container The container to convert. * @param scope The container scope. * @return The DN from the container and the computed scope. */ protected abstract String toDn(V container, ContainerScope scope); /** * Return the internal representation of the container set. Not existing cache items are removed. * * @param cacheItems The database base cache containers to convert. * @return The internal representation of container set. Ordered is kept. */ protected Set toInternal(final Collection cacheItems) { final var all = getRepository().findAll(); return cacheItems.stream().map(CacheContainer::getId).map(all::get) .filter(Objects::nonNull).collect(Collectors.toCollection(LinkedHashSet::new)); } /** * Return the internal representation of the container set as a {@link Page}. * * @param cacheItems The database base page cache containers to convert. * @return The internal representation of {@link org.ligoj.app.iam.model.CacheCompany} set. Ordered by the name. */ protected Page toInternal(final Page cacheItems) { return new PageImpl<>(new ArrayList<>(toInternal(cacheItems.getContent())), cacheItems.getPageable(), cacheItems.getTotalElements()); } /** * Return the closest {@link ContainerScope} name associated to the given container. Order of scopes is important * since the first matching item from this list is returned. * * @param scopes The available scopes. * @param container The containers to check. * @return The closest {@link ContainerScope} or null if not found. */ public ContainerScope toScope(final List scopes, final ContainerOrg container) { return scopes.stream().filter(s -> DnUtils.equalsOrParentOf(s.getDn(), container.getDn())).findFirst() .orElse(null); } /** * Simple transformer, securing sensible date. DN is not forwarded. * * @param rawGroup The group to convert. * @return The container including the scope and without sensible data. */ protected ContainerWithScopeVo toVo(final T rawGroup) { // Find the closest type final var securedUserOrg = new ContainerWithScopeVo(); final var scopes = containerScopeResource.findAllDescOrder(type); final var scope = toScope(scopes, rawGroup); NamedBean.copy(rawGroup, securedUserOrg); if (scope != null) { securedUserOrg.setScope(scope.getName()); } return securedUserOrg; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy