/*
* 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.persistence.jpa.dao;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.IterableUtils;
import org.apache.commons.collections4.Predicate;
import org.apache.commons.collections4.Transformer;
import org.apache.syncope.common.lib.types.AnyEntitlement;
import org.apache.syncope.common.lib.types.AnyTypeKind;
import org.apache.syncope.core.spring.security.AuthContextUtils;
import org.apache.syncope.core.spring.security.DelegatedAdministrationException;
import org.apache.syncope.core.provisioning.api.utils.EntityUtils;
import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
import org.apache.syncope.core.persistence.api.dao.GroupDAO;
import org.apache.syncope.core.persistence.api.dao.NotFoundException;
import org.apache.syncope.core.persistence.api.dao.UserDAO;
import org.apache.syncope.core.persistence.api.entity.AnyType;
import org.apache.syncope.core.persistence.api.entity.AnyUtils;
import org.apache.syncope.core.persistence.api.entity.Realm;
import org.apache.syncope.core.persistence.api.entity.anyobject.AMembership;
import org.apache.syncope.core.persistence.api.entity.anyobject.ARelationship;
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.user.URelationship;
import org.apache.syncope.core.persistence.jpa.entity.JPAAnyUtilsFactory;
import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAADynGroupMembership;
import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAARelationship;
import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAAnyObject;
import org.apache.syncope.core.persistence.jpa.entity.group.JPAGroup;
import org.apache.syncope.core.persistence.jpa.entity.user.JPAURelationship;
import org.apache.syncope.core.spring.ApplicationContextProvider;
import org.apache.syncope.core.spring.event.AnyCreatedUpdatedEvent;
import org.apache.syncope.core.spring.event.AnyDeletedEvent;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Repository
public class JPAAnyObjectDAO extends AbstractAnyDAO<AnyObject> implements AnyObjectDAO {
private UserDAO userDAO;
private GroupDAO groupDAO;
private UserDAO userDAO() {
synchronized (this) {
if (userDAO == null) {
userDAO = ApplicationContextProvider.getApplicationContext().getBean(UserDAO.class);
}
}
return userDAO;
}
private GroupDAO groupDAO() {
synchronized (this) {
if (groupDAO == null) {
groupDAO = ApplicationContextProvider.getApplicationContext().getBean(GroupDAO.class);
}
}
return groupDAO;
}
@Override
public Map<AnyType, Integer> countByType() {
Query query = entityManager().createQuery(
"SELECT e.type, COUNT(e) AS countByType FROM " + JPAAnyObject.class.getSimpleName() + " e "
+ "GROUP BY e.type ORDER BY countByType DESC");
@SuppressWarnings("unchecked")
List<Object[]> results = query.getResultList();
Map<AnyType, Integer> countByRealm = new LinkedHashMap<>(results.size());
for (Object[] result : results) {
countByRealm.put((AnyType) result[0], ((Number) result[1]).intValue());
}
return Collections.unmodifiableMap(countByRealm);
}
@Override
public Map<String, Integer> countByRealm(final AnyType anyType) {
Query query = entityManager().createQuery(
"SELECT e.realm, COUNT(e) FROM " + JPAAnyObject.class.getSimpleName() + " e "
+ "WHERE e.type=:type GROUP BY e.realm");
query.setParameter("type", anyType);
@SuppressWarnings("unchecked")
List<Object[]> results = query.getResultList();
Map<String, Integer> countByRealm = new HashMap<>(results.size());
for (Object[] result : results) {
countByRealm.put(((Realm) result[0]).getFullPath(), ((Number) result[1]).intValue());
}
return Collections.unmodifiableMap(countByRealm);
}
@Override
protected AnyUtils init() {
return new JPAAnyUtilsFactory().getInstance(AnyTypeKind.ANY_OBJECT);
}
@Override
protected void securityChecks(final AnyObject anyObject) {
Set<String> authRealms = AuthContextUtils.getAuthorizations().get(
AnyEntitlement.READ.getFor(anyObject.getType().getKey()));
boolean authorized = IterableUtils.matchesAny(authRealms, new Predicate<String>() {
@Override
public boolean evaluate(final String realm) {
return anyObject.getRealm().getFullPath().startsWith(realm);
}
});
if (authRealms == null || authRealms.isEmpty() || !authorized) {
throw new DelegatedAdministrationException(AnyTypeKind.ANY_OBJECT, anyObject.getKey());
}
}
@Override
public AnyObject findByName(final String name) {
TypedQuery<AnyObject> query = entityManager().createQuery(
"SELECT e FROM " + JPAAnyObject.class.getSimpleName() + " e WHERE e.name = :name", AnyObject.class);
query.setParameter("name", name);
AnyObject result = null;
try {
result = query.getSingleResult();
} catch (NoResultException e) {
LOG.debug("No any object found with name {}", name, e);
}
return result;
}
@Override
public AnyObject authFindByName(final String name) {
if (name == null) {
throw new NotFoundException("Null name");
}
AnyObject anyObject = findByName(name);
if (anyObject == null) {
throw new NotFoundException("Any Object " + name);
}
securityChecks(anyObject);
return anyObject;
}
@Override
public List<ARelationship> findAllARelationships(final AnyObject anyObject) {
TypedQuery<ARelationship> query = entityManager().createQuery(
"SELECT e FROM " + JPAARelationship.class.getSimpleName()
+ " e WHERE e.rightEnd=:anyObject OR e.leftEnd=:anyObject", ARelationship.class);
query.setParameter("anyObject", anyObject);
return query.getResultList();
}
@Override
public int count() {
Query query = entityManager().createQuery(
"SELECT COUNT(e) FROM " + JPAAnyObject.class.getSimpleName() + " e");
return ((Number) query.getSingleResult()).intValue();
}
@Override
public List<AnyObject> findAll(final int page, final int itemsPerPage) {
TypedQuery<AnyObject> query = entityManager().createQuery(
"SELECT e FROM " + JPAAnyObject.class.getSimpleName() + " e", AnyObject.class);
query.setFirstResult(itemsPerPage * (page <= 0 ? 0 : page - 1));
query.setMaxResults(itemsPerPage);
return query.getResultList();
}
@Override
public AnyObject save(final AnyObject anyObject) {
AnyObject merged = super.save(anyObject);
publisher.publishEvent(new AnyCreatedUpdatedEvent<>(this, merged));
groupDAO().refreshDynMemberships(merged);
return merged;
}
private List<ARelationship> findARelationships(final AnyObject anyObject) {
TypedQuery<ARelationship> query = entityManager().createQuery(
"SELECT e FROM " + JPAARelationship.class.getSimpleName()
+ " e WHERE e.rightEnd=:anyObject", ARelationship.class);
query.setParameter("anyObject", anyObject);
return query.getResultList();
}
private List<URelationship> findURelationships(final AnyObject anyObject) {
TypedQuery<URelationship> query = entityManager().createQuery(
"SELECT e FROM " + JPAURelationship.class.getSimpleName()
+ " e WHERE e.rightEnd=:anyObject", URelationship.class);
query.setParameter("anyObject", anyObject);
return query.getResultList();
}
@Override
public void delete(final AnyObject any) {
for (Group group : findDynGroupMemberships(any)) {
group.getADynMembership(any.getType()).getMembers().remove(any);
}
for (ARelationship relationship : findARelationships(any)) {
relationship.getLeftEnd().getRelationships().remove(relationship);
save(relationship.getLeftEnd());
entityManager().remove(relationship);
}
for (URelationship relationship : findURelationships(any)) {
relationship.getLeftEnd().getRelationships().remove(relationship);
userDAO().save(relationship.getLeftEnd());
entityManager().remove(relationship);
}
entityManager().remove(any);
publisher.publishEvent(new AnyDeletedEvent(this, AnyTypeKind.ANY_OBJECT, any.getKey()));
}
@Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true)
@Override
public List<Group> findDynGroupMemberships(final AnyObject anyObject) {
Query query = entityManager().createNativeQuery(
"SELECT t2.id FROM " + JPAADynGroupMembership.TABLE + " t0 "
+ "INNER JOIN ADynGroupMembership_AnyObject t1 "
+ "ON t0.id = t1.aDynGroupMembership_id "
+ "LEFT OUTER JOIN " + JPAGroup.TABLE + " t2 "
+ "ON t0.GROUP_ID = t2.id "
+ "WHERE t1.anyObject_id = ?1");
query.setParameter(1, anyObject.getKey());
List<Group> result = new ArrayList<>();
for (Object key : query.getResultList()) {
String actualKey = key instanceof Object[]
? (String) ((Object[]) key)[0]
: ((String) key);
Group group = groupDAO().find(actualKey);
if (group == null) {
LOG.error("Could not find group with id {}, even though returned by the native query", actualKey);
} else if (!result.contains(group)) {
result.add(group);
}
}
return result;
}
@Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true)
@Override
public Collection<Group> findAllGroups(final AnyObject anyObject) {
return CollectionUtils.union(
CollectionUtils.collect(anyObject.getMemberships(), new Transformer<AMembership, Group>() {
@Override
public Group transform(final AMembership input) {
return input.getRightEnd();
}
}, new ArrayList<Group>()),
findDynGroupMemberships(anyObject));
}
@Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true)
@Override
public Collection<String> findAllGroupKeys(final AnyObject anyObject) {
return CollectionUtils.collect(findAllGroups(anyObject), EntityUtils.<Group>keyTransformer());
}
@Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true)
@Override
public Collection<ExternalResource> findAllResources(final AnyObject anyObject) {
Set<ExternalResource> result = new HashSet<>();
result.addAll(anyObject.getResources());
for (Group group : findAllGroups(anyObject)) {
result.addAll(group.getResources());
}
return result;
}
@Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true)
@Override
public Collection<String> findAllResourceKeys(final String key) {
return CollectionUtils.collect(findAllResources(authFind(key)), EntityUtils.<ExternalResource>keyTransformer());
}
}