blob: 1b0c3a0daeaaa300cfad7c38581dfa920943ba0e [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.provisioning.java.pushpull;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.MutablePair;
import org.apache.syncope.common.lib.SyncopeConstants;
import org.apache.syncope.common.lib.types.ConflictResolutionAction;
import org.apache.syncope.core.persistence.api.search.SearchCondConverter;
import org.apache.syncope.core.spring.ApplicationContextProvider;
import org.apache.syncope.core.persistence.api.dao.AnyDAO;
import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
import org.apache.syncope.core.persistence.api.dao.RealmDAO;
import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
import org.apache.syncope.core.persistence.api.entity.Any;
import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
import org.apache.syncope.core.persistence.api.entity.Realm;
import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
import org.apache.syncope.core.persistence.api.entity.group.Group;
import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
import org.apache.syncope.core.persistence.api.entity.resource.Provision;
import org.apache.syncope.core.persistence.api.entity.task.PushTask;
import org.apache.syncope.core.persistence.api.entity.task.PushTaskAnyFilter;
import org.apache.syncope.core.persistence.api.entity.user.User;
import org.apache.syncope.core.persistence.api.search.SearchCondVisitor;
import org.apache.syncope.core.provisioning.api.Connector;
import org.apache.syncope.core.provisioning.api.ProvisionSorter;
import org.apache.syncope.core.provisioning.api.pushpull.AnyObjectPushResultHandler;
import org.apache.syncope.core.provisioning.api.pushpull.GroupPushResultHandler;
import org.apache.syncope.core.provisioning.api.pushpull.ProvisioningProfile;
import org.apache.syncope.core.provisioning.api.pushpull.PushActions;
import org.apache.syncope.core.provisioning.api.pushpull.RealmPushResultHandler;
import org.apache.syncope.core.provisioning.api.pushpull.SyncopePushResultHandler;
import org.apache.syncope.core.provisioning.api.pushpull.UserPushResultHandler;
import org.apache.syncope.core.spring.ImplementationManager;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
public class PushJobDelegate extends AbstractProvisioningJobDelegate<PushTask> {
/**
* Search DAO.
*/
@Autowired
protected AnySearchDAO searchDAO;
@Autowired
protected RealmDAO realmDAO;
@Autowired
protected AnyUtilsFactory anyUtilsFactory;
@Autowired
protected SearchCondVisitor searchCondVisitor;
protected ProvisioningProfile<PushTask, PushActions> profile;
protected final Map<String, MutablePair<Integer, String>> handled = new HashMap<>();
protected void reportHandled(final String anyType, final String key) {
MutablePair<Integer, String> pair = handled.get(anyType);
if (pair == null) {
pair = MutablePair.of(0, null);
handled.put(anyType, pair);
}
pair.setLeft(pair.getLeft() + 1);
pair.setRight(key);
}
@Override
public String currentStatus() {
synchronized (status) {
if (!handled.isEmpty()) {
StringBuilder builder = new StringBuilder("Processed:\n");
handled.forEach((key, value) -> builder.append(' ').append(value.getLeft()).append('\t').
append(key).
append(" / latest: ").append(value.getRight()).
append('\n'));
status.set(builder.toString());
}
}
return status.get();
}
protected void doHandle(
final List<? extends Any<?>> anys,
final SyncopePushResultHandler handler,
final ExternalResource resource)
throws JobExecutionException {
for (int i = 0; i < anys.size() && !interrupt; i++) {
try {
handler.handle(anys.get(i).getKey());
reportHandled(
anys.get(i).getType().getKey(),
(anys.get(i) instanceof User
? ((User) anys.get(i)).getUsername()
: anys.get(i) instanceof Group
? ((Group) anys.get(i)).getName()
: ((AnyObject) anys.get(i)).getName()));
} catch (Exception e) {
LOG.warn("Failure pushing '{}' on '{}'", anys.get(i), resource, e);
throw new JobExecutionException("While pushing " + anys.get(i) + " on " + resource, e);
}
}
}
protected RealmPushResultHandler buildRealmHandler() {
return (RealmPushResultHandler) ApplicationContextProvider.getBeanFactory().
createBean(DefaultRealmPushResultHandler.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false);
}
protected AnyObjectPushResultHandler buildAnyObjectHandler() {
return (AnyObjectPushResultHandler) ApplicationContextProvider.getBeanFactory().
createBean(DefaultAnyObjectPushResultHandler.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false);
}
protected UserPushResultHandler buildUserHandler() {
return (UserPushResultHandler) ApplicationContextProvider.getBeanFactory().
createBean(DefaultUserPushResultHandler.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false);
}
protected GroupPushResultHandler buildGroupHandler() {
return (GroupPushResultHandler) ApplicationContextProvider.getBeanFactory().
createBean(DefaultGroupPushResultHandler.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, false);
}
@Override
protected String doExecuteProvisioning(
final PushTask pushTask,
final Connector connector,
final boolean dryRun,
final String executor,
final JobExecutionContext context) throws JobExecutionException {
LOG.debug("Executing push on {}", pushTask.getResource());
List<PushActions> actions = new ArrayList<>();
pushTask.getActions().forEach(impl -> {
try {
actions.add(ImplementationManager.build(impl));
} catch (Exception e) {
LOG.warn("While building {}", impl, e);
}
});
profile = new ProvisioningProfile<>(connector, pushTask);
profile.getActions().addAll(actions);
profile.setDryRun(dryRun);
profile.setConflictResolutionAction(pushTask.getResource().getPushPolicy() == null
? ConflictResolutionAction.IGNORE
: pushTask.getResource().getPushPolicy().getConflictResolutionAction());
profile.setExecutor(executor);
if (!profile.isDryRun()) {
for (PushActions action : actions) {
action.beforeAll(profile);
}
}
status.set("Initialization completed");
// First realms...
if (pushTask.getResource().getOrgUnit() != null) {
status.set("Pushing realms");
RealmPushResultHandler handler = buildRealmHandler();
handler.setProfile(profile);
for (Realm realm : realmDAO.findDescendants(profile.getTask().getSourceRealm())) {
// Never push the root realm
if (realm.getParent() != null) {
try {
handler.handle(realm.getKey());
reportHandled(SyncopeConstants.REALM_ANYTYPE, realm.getName());
} catch (Exception e) {
LOG.warn("Failure pushing '{}' on '{}'", realm, pushTask.getResource(), e);
throw new JobExecutionException("While pushing " + realm + " on " + pushTask.getResource(), e);
}
}
}
}
// ...then provisions for any types
ProvisionSorter provisionSorter = new DefaultProvisionSorter();
if (pushTask.getResource().getProvisionSorter() != null) {
try {
provisionSorter = ImplementationManager.build(pushTask.getResource().getProvisionSorter());
} catch (Exception e) {
LOG.error("While building {}", pushTask.getResource().getProvisionSorter(), e);
}
}
for (Provision provision : pushTask.getResource().getProvisions().stream().
filter(provision -> provision.getMapping() != null).sorted(provisionSorter).
collect(Collectors.toList())) {
status.set("Pushing " + provision.getAnyType().getKey());
AnyDAO<?> anyDAO = anyUtilsFactory.getInstance(provision.getAnyType().getKind()).dao();
SyncopePushResultHandler handler;
switch (provision.getAnyType().getKind()) {
case USER:
handler = buildUserHandler();
break;
case GROUP:
handler = buildGroupHandler();
break;
case ANY_OBJECT:
default:
handler = buildAnyObjectHandler();
}
handler.setProfile(profile);
Optional<? extends PushTaskAnyFilter> anyFilter = pushTask.getFilter(provision.getAnyType());
String filter = anyFilter.map(PushTaskAnyFilter::getFIQLCond).orElse(null);
SearchCond cond = StringUtils.isBlank(filter)
? anyDAO.getAllMatchingCond()
: SearchCondConverter.convert(searchCondVisitor, filter);
int count = searchDAO.count(
Set.of(profile.getTask().getSourceRealm().getFullPath()),
cond,
provision.getAnyType().getKind());
for (int page = 1; page <= (count / AnyDAO.DEFAULT_PAGE_SIZE) + 1 && !interrupt; page++) {
List<? extends Any<?>> anys = searchDAO.search(
Set.of(profile.getTask().getSourceRealm().getFullPath()),
cond,
page,
AnyDAO.DEFAULT_PAGE_SIZE,
List.of(),
provision.getAnyType().getKind());
doHandle(anys, handler, pushTask.getResource());
}
}
if (!profile.isDryRun() && !interrupt) {
for (PushActions action : actions) {
action.afterAll(profile);
}
}
if (interrupt) {
interrupted = true;
}
status.set("Push done");
String result = createReport(profile.getResults(), pushTask.getResource(), dryRun);
LOG.debug("Push result: {}", result);
return result;
}
}