Java tutorial
/* * 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.syncope.core.persistence.dao.impl; import java.io.StringReader; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.regex.Pattern; import javax.persistence.NoResultException; import javax.persistence.Query; import javax.persistence.TemporalType; import javax.persistence.TypedQuery; import org.apache.commons.jexl2.parser.Parser; import org.apache.commons.jexl2.parser.ParserConstants; import org.apache.commons.jexl2.parser.Token; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; import org.springframework.util.StringUtils; import org.syncope.core.persistence.beans.AbstractAttrValue; import org.syncope.core.persistence.beans.AbstractVirAttr; import org.syncope.core.persistence.beans.ExternalResource; import org.syncope.core.persistence.beans.PropagationTask; import org.syncope.core.persistence.beans.membership.Membership; import org.syncope.core.persistence.beans.user.SyncopeUser; import org.syncope.core.persistence.beans.user.UAttrUniqueValue; import org.syncope.core.persistence.beans.user.UAttrValue; import org.syncope.core.persistence.beans.user.UDerSchema; import org.syncope.core.persistence.beans.user.USchema; import org.syncope.core.persistence.dao.DerSchemaDAO; import org.syncope.core.persistence.dao.RoleDAO; import org.syncope.core.persistence.dao.SchemaDAO; import org.syncope.core.persistence.dao.TaskDAO; import org.syncope.core.persistence.dao.UserDAO; import org.syncope.core.rest.controller.InvalidSearchConditionException; @Repository public class UserDAOImpl extends AbstractDAOImpl implements UserDAO { @Autowired private SchemaDAO schemaDAO; @Autowired private DerSchemaDAO derSchemaDAO; @Autowired private RoleDAO roleDAO; @Autowired private TaskDAO taskDAO; @Override public SyncopeUser find(final Long id) { TypedQuery<SyncopeUser> query = entityManager.createQuery( "SELECT e FROM " + SyncopeUser.class.getSimpleName() + " e " + "WHERE e.id = :id", SyncopeUser.class); query.setParameter("id", id); SyncopeUser result = null; try { return query.getSingleResult(); } catch (NoResultException e) { } return result; } @Override public SyncopeUser find(final String username) { TypedQuery<SyncopeUser> query = entityManager.createQuery( "SELECT e FROM " + SyncopeUser.class.getSimpleName() + " e " + "WHERE e.username = :username", SyncopeUser.class); query.setParameter("username", username); SyncopeUser result = null; try { return query.getSingleResult(); } catch (NoResultException e) { } return result; } @Override public SyncopeUser findByWorkflowId(final String workflowId) { TypedQuery<SyncopeUser> query = entityManager.createQuery( "SELECT e FROM " + SyncopeUser.class.getSimpleName() + " e " + "WHERE e.workflowId = :workflowId", SyncopeUser.class); query.setParameter("workflowId", workflowId); return query.getSingleResult(); } /** * Find users by derived attribute value. This method could fail if one or * more string literals contained into the derived attribute value provided * derive from identifier (schema name) replacement. When you are going to * specify a derived attribute expression you must be quite sure that string * literals used to build the expression cannot be found into the attribute * values used to replace attribute schema names used as identifiers. * * @param schemaName derived schema name. * @param value derived attribute value. * @return list of users. * @throws InvalidSearchConditionException in case of errors retrieving * schema names used to buid the derived schema expression. */ @Override public List<SyncopeUser> findByDerAttrValue(final String schemaName, final String value) throws InvalidSearchConditionException { UDerSchema schema = derSchemaDAO.find(schemaName, UDerSchema.class); if (schema == null) { LOG.error("Invalid schema name '{}'", schemaName); return Collections.EMPTY_LIST; } // query string final StringBuilder querystring = new StringBuilder(); boolean subquery = false; for (String clause : getWhereClause(schema.getExpression(), value)) { if (querystring.length() > 0) { subquery = true; querystring.append(" AND a.owner_id IN ( "); } querystring.append("SELECT a.owner_id ").append("FROM UAttr a, UAttrValue v, USchema s ") .append("WHERE ").append(clause); if (subquery) { querystring.append(')'); } } LOG.debug("Execute query {}", querystring); final Query query = entityManager.createNativeQuery(querystring.toString()); final List<SyncopeUser> result = new ArrayList<SyncopeUser>(); SyncopeUser user; for (Object userId : query.getResultList()) { user = find(Long.parseLong(userId.toString())); if (!result.contains(user)) { result.add(user); } } return result; } @Override public List<SyncopeUser> findByAttrValue(final String schemaName, final UAttrValue attrValue) { USchema schema = schemaDAO.find(schemaName, USchema.class); if (schema == null) { LOG.error("Invalid schema name '{}'", schemaName); return Collections.EMPTY_LIST; } final String entityName = schema.isUniqueConstraint() ? UAttrUniqueValue.class.getName() : UAttrValue.class.getName(); Query query = entityManager .createQuery("SELECT e FROM " + entityName + " e" + " WHERE e.attribute.schema.name = :schemaName " + " AND (e.stringValue IS NOT NULL" + " AND e.stringValue = :stringValue)" + " OR (e.booleanValue IS NOT NULL" + " AND e.booleanValue = :booleanValue)" + " OR (e.dateValue IS NOT NULL" + " AND e.dateValue = :dateValue)" + " OR (e.longValue IS NOT NULL" + " AND e.longValue = :longValue)" + " OR (e.doubleValue IS NOT NULL" + " AND e.doubleValue = :doubleValue)"); query.setParameter("schemaName", schemaName); query.setParameter("stringValue", attrValue.getStringValue()); query.setParameter("booleanValue", attrValue.getBooleanValue() == null ? null : attrValue.getBooleanAsInteger(attrValue.getBooleanValue())); if (attrValue.getDateValue() != null) { query.setParameter("dateValue", attrValue.getDateValue(), TemporalType.TIMESTAMP); } else { query.setParameter("dateValue", null); } query.setParameter("longValue", attrValue.getLongValue()); query.setParameter("doubleValue", attrValue.getDoubleValue()); List<SyncopeUser> result = new ArrayList<SyncopeUser>(); SyncopeUser user; for (AbstractAttrValue value : (List<AbstractAttrValue>) query.getResultList()) { user = (SyncopeUser) value.getAttribute().getOwner(); if (!result.contains(user)) { result.add(user); } } return result; } @Override public SyncopeUser findByAttrUniqueValue(final String schemaName, final UAttrValue attrUniqueValue) { USchema schema = schemaDAO.find(schemaName, USchema.class); if (schema == null) { LOG.error("Invalid schema name '{}'", schemaName); return null; } if (!schema.isUniqueConstraint()) { LOG.error("This schema has not unique constraint: '{}'", schemaName); return null; } List<SyncopeUser> result = findByAttrValue(schemaName, attrUniqueValue); return result.isEmpty() ? null : result.iterator().next(); } @Override public List<SyncopeUser> findByResource(final ExternalResource resource) { Query query = entityManager.createQuery("SELECT e FROM " + SyncopeUser.class.getSimpleName() + " e " + "WHERE :resource MEMBER OF e.resources"); query.setParameter("resource", resource); return query.getResultList(); } private StringBuilder getFindAllQuery(final Set<Long> adminRoles) { final StringBuilder queryString = new StringBuilder("SELECT id FROM SyncopeUser WHERE id NOT IN ("); if (adminRoles == null || adminRoles.isEmpty()) { queryString.append("SELECT syncopeUser_id AS id FROM Membership"); } else { queryString.append("SELECT syncopeUser_id FROM Membership M1 ").append("WHERE syncopeRole_id IN ("); queryString.append("SELECT syncopeRole_id FROM Membership M2 ") .append("WHERE M2.syncopeUser_id=M1.syncopeUser_id ").append("AND syncopeRole_id NOT IN ("); queryString.append("SELECT id AS syncopeRole_id FROM SyncopeRole"); boolean firstRole = true; for (Long adminRoleId : adminRoles) { if (firstRole) { queryString.append(" WHERE"); firstRole = false; } else { queryString.append(" OR"); } queryString.append(" id=").append(adminRoleId); } queryString.append("))"); } queryString.append(")"); return queryString; } @Override public final List<SyncopeUser> findAll(final Set<Long> adminRoles) { return findAll(adminRoles, -1, -1); } @Override public final List<SyncopeUser> findAll(final Set<Long> adminRoles, final int page, final int itemsPerPage) { final Query query = entityManager.createNativeQuery(getFindAllQuery(adminRoles).toString()); query.setFirstResult(itemsPerPage * (page <= 0 ? 0 : page - 1)); if (itemsPerPage > 0) { query.setMaxResults(itemsPerPage); } List<Number> userIds = new ArrayList<Number>(); List resultList = query.getResultList(); //fix for HHH-5902 - bug hibernate if (resultList != null) { for (Object userId : resultList) { if (userId instanceof Object[]) { userIds.add((Number) ((Object[]) userId)[0]); } else { userIds.add((Number) userId); } } } List<SyncopeUser> result = new ArrayList<SyncopeUser>(userIds.size()); SyncopeUser user; for (Object userId : userIds) { user = find(((Number) userId).longValue()); if (user == null) { LOG.error("Could not find user with id {}, " + "even though returned by the native query", userId); } else { result.add(user); } } return result; } @Override public final int count(final Set<Long> adminRoles) { StringBuilder queryString = getFindAllQuery(adminRoles); queryString.insert(0, "SELECT COUNT(id) FROM ("); queryString.append(") count_user_id"); Query countQuery = entityManager.createNativeQuery(queryString.toString()); return ((Number) countQuery.getSingleResult()).intValue(); } @Override public SyncopeUser save(final SyncopeUser user) { SyncopeUser merged = entityManager.merge(user); for (AbstractVirAttr virtual : merged.getVirtualAttributes()) { virtual.setValues(user.getVirtualAttribute(virtual.getVirtualSchema().getName()).getValues()); } return merged; } @Override public void delete(final Long id) { SyncopeUser user = find(id); if (user == null) { return; } delete(user); } @Override public void delete(final SyncopeUser user) { // Not calling membershipDAO.delete() here because it would try // to save this user as well, thus going into // ConcurrentModificationException for (Membership membership : user.getMemberships()) { membership.setSyncopeUser(null); roleDAO.save(membership.getSyncopeRole()); membership.setSyncopeRole(null); entityManager.remove(membership); } user.getMemberships().clear(); for (PropagationTask task : taskDAO.findAll(user)) { task.setSyncopeUser(null); } entityManager.remove(user); } /** * Generate one where clause for each different attribute schema into the * derived schema expression provided. * * @param expression derived schema expression. * @param value derived attribute value. * @return where clauses to use to build the query. * @throws InvalidSearchConditionException in case of errors retrieving * identifiers. */ private Set<String> getWhereClause(final String expression, final String value) throws InvalidSearchConditionException { final Parser parser = new Parser(new StringReader(expression)); // Schema names final List<String> identifiers = new ArrayList<String>(); // Literals final List<String> literals = new ArrayList<String>(); // Get schema names and literals Token token; while ((token = parser.getNextToken()) != null && StringUtils.hasText(token.toString())) { if (token.kind == ParserConstants.STRING_LITERAL) { literals.add(token.toString().substring(1, token.toString().length() - 1)); } if (token.kind == ParserConstants.IDENTIFIER) { identifiers.add(token.toString()); } } // Sort literals in order to process later literals included into others Collections.sort(literals, new Comparator<String>() { @Override public int compare(String t, String t1) { if (t == null && t1 == null) { return 0; } if (t != null && t1 == null) { return -1; } if (t == null && t1 != null) { return 1; } if (t.length() == t1.length()) { return 0; } if (t.length() > t1.length()) { return -1; } else { return 1; } } }); // Split value on provided literals final List<String> attrValues = split(value, literals); if (attrValues.size() != identifiers.size()) { LOG.error("Ambiguous jexl expression resolution."); throw new InvalidSearchConditionException("literals and values have different size"); } // clauses to be used with INTERSECTed queries final Set<String> clauses = new HashSet<String>(); // builder to build the clauses final StringBuilder bld = new StringBuilder(); // Contains used identifiers in order to avoid replications final Set<String> used = new HashSet<String>(); USchema schema; // Create several clauses: one for eanch identifiers for (int i = 0; i < identifiers.size(); i++) { if (!used.contains(identifiers.get(i))) { // verify schema existence and get schema type schema = schemaDAO.find(identifiers.get(i), USchema.class); if (schema == null) { LOG.error("Invalid schema name '{}'", identifiers.get(i)); throw new InvalidSearchConditionException("Invalid schema name " + identifiers.get(i)); } // clear builder bld.delete(0, bld.length()); bld.append("("); // set schema name bld.append("s.name = '").append(identifiers.get(i)).append("'"); bld.append(" AND "); bld.append("s.name = a.schema_name").append(" AND "); bld.append("a.id = v.attribute_id"); bld.append(" AND "); // use a value clause different for eanch different schema type switch (schema.getType()) { case Boolean: bld.append("v.booleanValue = '").append(attrValues.get(i)).append("'"); break; case Long: bld.append("v.longValue = ").append(attrValues.get(i)); break; case Double: bld.append("v.doubleValue = ").append(attrValues.get(i)); break; case Date: bld.append("v.dateValue = '").append(attrValues.get(i)).append("'"); break; default: bld.append("v.stringValue = '").append(attrValues.get(i)).append("'"); } bld.append(")"); used.add(identifiers.get(i)); clauses.add(bld.toString()); } } LOG.debug("Generated where clauses {}", clauses); return clauses; } /** * Split an attribute value recurring on provided literals/tokens. * * @param attrValue value to be splitted. * @param literals literals/tokens. * @return */ private List<String> split(final String attrValue, final List<String> literals) { final List<String> attrValues = new ArrayList<String>(); if (literals.isEmpty()) { attrValues.add(attrValue); } else { for (String token : attrValue.split(Pattern.quote(literals.get(0)))) { attrValues.addAll(split(token, literals.subList(1, literals.size()))); } } return attrValues; } }