blob: 2eb2cda6326f06351ab30f2b5eb61f12475c6f19 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.syncope.core.rest.cxf.service;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.core.Response;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.syncope.common.lib.SyncopeConstants;
import org.apache.syncope.common.lib.request.AnyCR;
import org.apache.syncope.common.lib.request.AnyUR;
import org.apache.syncope.common.lib.request.ResourceAR;
import org.apache.syncope.common.lib.request.AttrPatch;
import org.apache.syncope.common.lib.request.ResourceDR;
import org.apache.syncope.common.lib.search.SpecialAttr;
import org.apache.syncope.common.lib.to.AnyTO;
import org.apache.syncope.common.lib.Attr;
import org.apache.syncope.common.lib.to.PagedResult;
import org.apache.syncope.common.lib.to.ProvisioningResult;
import org.apache.syncope.common.lib.types.PatchOperation;
import org.apache.syncope.common.lib.types.ResourceAssociationAction;
import org.apache.syncope.common.lib.types.ResourceDeassociationAction;
import org.apache.syncope.common.lib.types.SchemaType;
import org.apache.syncope.common.rest.api.Preference;
import org.apache.syncope.common.rest.api.RESTHeaders;
import org.apache.syncope.common.rest.api.batch.BatchPayloadGenerator;
import org.apache.syncope.common.rest.api.batch.BatchResponseItem;
import org.apache.syncope.common.rest.api.beans.AnyQuery;
import org.apache.syncope.common.rest.api.service.AnyService;
import org.apache.syncope.core.logic.AbstractAnyLogic;
import org.apache.syncope.core.persistence.api.dao.AnyDAO;
import org.apache.syncope.core.persistence.api.dao.NotFoundException;
import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
import org.apache.syncope.core.persistence.api.search.SearchCondVisitor;
import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
import org.apache.syncope.core.spring.security.SecureRandomUtils;
public abstract class AbstractAnyService<TO extends AnyTO, CR extends AnyCR, UR extends AnyUR>
extends AbstractSearchService implements AnyService<TO> {
public AbstractAnyService(final SearchCondVisitor searchCondVisitor) {
super(searchCondVisitor);
}
protected abstract AnyDAO<?> getAnyDAO();
protected abstract AbstractAnyLogic<TO, CR, UR> getAnyLogic();
protected abstract UR newUpdateReq(String key);
@Override
public Set<Attr> read(final String key, final SchemaType schemaType) {
TO any = read(key);
Set<Attr> result;
switch (schemaType) {
case DERIVED:
result = any.getDerAttrs();
break;
case VIRTUAL:
result = any.getVirAttrs();
break;
case PLAIN:
default:
result = any.getPlainAttrs();
}
return result;
}
@Override
public Attr read(final String key, final SchemaType schemaType, final String schema) {
TO any = read(key);
Optional<Attr> result;
switch (schemaType) {
case DERIVED:
result = any.getDerAttr(schema);
break;
case VIRTUAL:
result = any.getVirAttr(schema);
break;
case PLAIN:
default:
result = any.getPlainAttr(schema);
}
if (result.isEmpty()) {
throw new NotFoundException("Attribute for type " + schemaType + " and schema " + schema);
}
return result.get();
}
@Override
public TO read(final String key) {
return getAnyLogic().read(getActualKey(getAnyDAO(), key));
}
@Override
public PagedResult<TO> search(final AnyQuery anyQuery) {
String realm = StringUtils.prependIfMissing(anyQuery.getRealm(), SyncopeConstants.ROOT_REALM);
// if an assignable query is provided in the FIQL string, start anyway from root realm
boolean isAssignableCond = StringUtils.isBlank(anyQuery.getFiql())
? false
: -1 != anyQuery.getFiql().indexOf(SpecialAttr.ASSIGNABLE.toString());
SearchCond searchCond = StringUtils.isBlank(anyQuery.getFiql())
? null
: getSearchCond(anyQuery.getFiql(), realm);
Pair<Integer, List<TO>> result = getAnyLogic().search(
searchCond,
anyQuery.getPage(),
anyQuery.getSize(),
getOrderByClauses(anyQuery.getOrderBy()),
isAssignableCond ? SyncopeConstants.ROOT_REALM : realm,
anyQuery.getDetails());
return buildPagedResult(result.getRight(), anyQuery.getPage(), anyQuery.getSize(), result.getLeft());
}
protected Date findLastChange(final String key) {
Date lastChange = getAnyDAO().findLastChange(key);
if (lastChange == null) {
throw new NotFoundException("User, Group or Any Object for " + key);
}
return lastChange;
}
protected Response doUpdate(final UR updateReq) {
updateReq.setKey(getActualKey(getAnyDAO(), updateReq.getKey()));
Date etagDate = findLastChange(updateReq.getKey());
checkETag(String.valueOf(etagDate.getTime()));
ProvisioningResult<TO> updated = getAnyLogic().update(updateReq, isNullPriorityAsync());
return modificationResponse(updated);
}
protected void addUpdateOrReplaceAttr(
final String key, final SchemaType schemaType, final Attr attrTO, final PatchOperation operation) {
if (attrTO.getSchema() == null) {
throw new NotFoundException("Must specify schema");
}
UR updateReq = newUpdateReq(key);
switch (schemaType) {
case VIRTUAL:
updateReq.getVirAttrs().add(attrTO);
break;
case PLAIN:
updateReq.getPlainAttrs().add(new AttrPatch.Builder(attrTO).operation(operation).build());
break;
case DERIVED:
default:
}
doUpdate(updateReq);
}
@Override
public Response update(final String key, final SchemaType schemaType, final Attr attrTO) {
String actualKey = getActualKey(getAnyDAO(), key);
addUpdateOrReplaceAttr(actualKey, schemaType, attrTO, PatchOperation.ADD_REPLACE);
return modificationResponse(read(actualKey, schemaType, attrTO.getSchema()));
}
@Override
public void delete(final String key, final SchemaType schemaType, final String schema) {
addUpdateOrReplaceAttr(
getActualKey(getAnyDAO(), key),
schemaType,
new Attr.Builder(schema).build(),
PatchOperation.DELETE);
}
@Override
public Response delete(final String key) {
String actualKey = getActualKey(getAnyDAO(), key);
Date etagDate = findLastChange(actualKey);
checkETag(String.valueOf(etagDate.getTime()));
ProvisioningResult<TO> deleted = getAnyLogic().delete(actualKey, isNullPriorityAsync());
return modificationResponse(deleted);
}
@Override
public Response deassociate(final ResourceDR req) {
Date etagDate = findLastChange(req.getKey());
checkETag(String.valueOf(etagDate.getTime()));
ProvisioningResult<TO> updated;
switch (req.getAction()) {
case UNLINK:
updated = new ProvisioningResult<>();
updated.setEntity(getAnyLogic().unlink(req.getKey(), req.getResources()));
break;
case UNASSIGN:
updated = getAnyLogic().unassign(req.getKey(), req.getResources(), isNullPriorityAsync());
break;
case DEPROVISION:
updated = getAnyLogic().deprovision(req.getKey(), req.getResources(), isNullPriorityAsync());
break;
default:
throw new BadRequestException("Missing action");
}
List<BatchResponseItem> batchResponseItems;
if (req.getAction() == ResourceDeassociationAction.UNLINK) {
batchResponseItems = req.getResources().stream().map(resource -> {
BatchResponseItem item = new BatchResponseItem();
item.getHeaders().put(RESTHeaders.RESOURCE_KEY, List.of(resource));
item.setStatus(updated.getEntity().getResources().contains(resource)
? Response.Status.BAD_REQUEST.getStatusCode()
: Response.Status.OK.getStatusCode());
if (getPreference() == Preference.RETURN_NO_CONTENT) {
item.getHeaders().put(
RESTHeaders.PREFERENCE_APPLIED,
List.of(Preference.RETURN_NO_CONTENT.toString()));
} else {
item.setContent(POJOHelper.serialize(updated.getEntity()));
}
return item;
}).collect(Collectors.toList());
} else {
batchResponseItems = updated.getPropagationStatuses().stream().
map(status -> {
BatchResponseItem item = new BatchResponseItem();
item.getHeaders().put(RESTHeaders.RESOURCE_KEY, List.of(status.getResource()));
item.setStatus(status.getStatus().getHttpStatus());
if (status.getFailureReason() != null) {
item.getHeaders().put(RESTHeaders.ERROR_INFO, List.of(status.getFailureReason()));
}
if (getPreference() == Preference.RETURN_NO_CONTENT) {
item.getHeaders().put(
RESTHeaders.PREFERENCE_APPLIED,
List.of(Preference.RETURN_NO_CONTENT.toString()));
} else {
item.setContent(POJOHelper.serialize(updated.getEntity()));
}
return item;
}).collect(Collectors.toList());
}
String boundary = "deassociate_" + SecureRandomUtils.generateRandomUUID().toString();
return Response.ok(BatchPayloadGenerator.generate(
batchResponseItems, SyncopeConstants.DOUBLE_DASH + boundary)).
type(RESTHeaders.multipartMixedWith(boundary)).
build();
}
@Override
public Response associate(final ResourceAR req) {
Date etagDate = findLastChange(req.getKey());
checkETag(String.valueOf(etagDate.getTime()));
ProvisioningResult<TO> updated;
switch (req.getAction()) {
case LINK:
updated = new ProvisioningResult<>();
updated.setEntity(getAnyLogic().link(
req.getKey(),
req.getResources()));
break;
case ASSIGN:
updated = getAnyLogic().assign(
req.getKey(),
req.getResources(),
req.getValue() != null,
req.getValue(),
isNullPriorityAsync());
break;
case PROVISION:
updated = getAnyLogic().provision(
req.getKey(),
req.getResources(),
req.getValue() != null,
req.getValue(),
isNullPriorityAsync());
break;
default:
throw new BadRequestException("Missing action");
}
List<BatchResponseItem> batchResponseItems;
if (req.getAction() == ResourceAssociationAction.LINK) {
batchResponseItems = req.getResources().stream().map(resource -> {
BatchResponseItem item = new BatchResponseItem();
item.getHeaders().put(RESTHeaders.RESOURCE_KEY, List.of(resource));
item.setStatus(updated.getEntity().getResources().contains(resource)
? Response.Status.OK.getStatusCode()
: Response.Status.BAD_REQUEST.getStatusCode());
if (getPreference() == Preference.RETURN_NO_CONTENT) {
item.getHeaders().put(
RESTHeaders.PREFERENCE_APPLIED,
List.of(Preference.RETURN_NO_CONTENT.toString()));
} else {
item.setContent(POJOHelper.serialize(updated.getEntity()));
}
return item;
}).collect(Collectors.toList());
} else {
batchResponseItems = updated.getPropagationStatuses().stream().
map(status -> {
BatchResponseItem item = new BatchResponseItem();
item.getHeaders().put(RESTHeaders.RESOURCE_KEY, List.of(status.getResource()));
item.setStatus(status.getStatus().getHttpStatus());
if (status.getFailureReason() != null) {
item.getHeaders().put(RESTHeaders.ERROR_INFO, List.of(status.getFailureReason()));
}
if (getPreference() == Preference.RETURN_NO_CONTENT) {
item.getHeaders().put(
RESTHeaders.PREFERENCE_APPLIED,
List.of(Preference.RETURN_NO_CONTENT.toString()));
} else {
item.setContent(POJOHelper.serialize(updated.getEntity()));
}
return item;
}).collect(Collectors.toList());
}
String boundary = "associate_" + SecureRandomUtils.generateRandomUUID().toString();
return Response.ok(BatchPayloadGenerator.generate(
batchResponseItems, SyncopeConstants.DOUBLE_DASH + boundary)).
type(RESTHeaders.multipartMixedWith(boundary)).
build();
}
}