Author: fmartelli Date: Wed Feb 13 10:26:16 2013 New Revision: 1445543 URL: http://svn.apache.org/r1445543 Log: SYNCOPE-154 added virtual attribute cache on the branch 1_0_X
Added: syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/util/VirAttrCache.java (with props) syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/util/VirAttrCacheKey.java (with props) syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/util/VirAttrCacheValue.java (with props) Modified: syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/propagation/PropagationManager.java syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/util/ConnObjectUtil.java syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/util/SchemaMappingUtil.java syncope/branches/1_0_X/core/src/main/resources/syncopeContext.xml syncope/branches/1_0_X/core/src/test/java/org/apache/syncope/core/rest/UserTestITCase.java Modified: syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/propagation/PropagationManager.java URL: http://svn.apache.org/viewvc/syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/propagation/PropagationManager.java?rev=1445543&r1=1445542&r2=1445543&view=diff ============================================================================== --- syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/propagation/PropagationManager.java (original) +++ syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/propagation/PropagationManager.java Wed Feb 13 10:26:16 2013 @@ -50,6 +50,7 @@ import org.apache.syncope.core.rest.data import org.apache.syncope.core.util.AttributableUtil; import org.apache.syncope.core.util.JexlUtil; import org.apache.syncope.core.util.SchemaMappingUtil; +import org.apache.syncope.core.util.VirAttrCache; import org.apache.syncope.core.workflow.WorkflowResult; import org.apache.syncope.types.AttributableType; import org.apache.syncope.types.IntMappingType; @@ -122,6 +123,12 @@ public class PropagationManager { private TaskDAO taskDAO; /** + * Virtual attribute cache. + */ + @Autowired + private VirAttrCache virAttrCache; + + /** * JEXL engine for evaluating connector's account link. */ @Autowired @@ -388,6 +395,10 @@ public class PropagationManager { schemaType = schema == null ? SchemaType.String : schema.getType(); break; + case UserVirtualSchema: + LOG.error("AAAAAAAAAAAAAAAAAAA Expire entry cache {}-{}", user.getId(), mapping.getIntAttrName()); + virAttrCache.expire(user.getId(), mapping.getIntAttrName()); + // no break .... default: schemaType = SchemaType.String; } Modified: syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/util/ConnObjectUtil.java URL: http://svn.apache.org/viewvc/syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/util/ConnObjectUtil.java?rev=1445543&r1=1445542&r2=1445543&view=diff ============================================================================== --- syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/util/ConnObjectUtil.java (original) +++ syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/util/ConnObjectUtil.java Wed Feb 13 10:26:16 2013 @@ -18,10 +18,10 @@ */ package org.apache.syncope.core.util; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import javassist.NotFoundException; @@ -45,6 +45,7 @@ import org.apache.syncope.core.persisten import org.apache.syncope.core.propagation.ConnectorFacadeProxy; import org.apache.syncope.core.rest.controller.UnauthorizedRoleException; import org.apache.syncope.core.rest.data.UserDataBinder; +import org.apache.syncope.types.IntMappingType; import org.identityconnectors.common.security.GuardedByteArray; import org.identityconnectors.common.security.GuardedString; import org.identityconnectors.framework.common.objects.Attribute; @@ -80,6 +81,12 @@ public class ConnObjectUtil { private UserDataBinder userDataBinder; /** + * Virtual attribute cache. + */ + @Autowired + private VirAttrCache virAttrCache; + + /** * Build an UserTO out of connector object attributes and schema mapping. * * @param obj connector object @@ -303,76 +310,96 @@ public class ConnObjectUtil { final ConfigurableApplicationContext context = ApplicationContextProvider.getApplicationContext(); final ConnInstanceLoader connInstanceLoader = context.getBean(ConnInstanceLoader.class); - final Map<SchemaMappingUtil.SchemaMappingsWrapper, ConnectorObject> remoteObjects = - new HashMap<SchemaMappingUtil.SchemaMappingsWrapper, ConnectorObject>(); + final IntMappingType type = owner instanceof SyncopeUser + ? IntMappingType.UserVirtualSchema : IntMappingType.RoleVirtualSchema; - for (ExternalResource resource : owner.getResources()) { - LOG.debug("Retrieve remote object from '{}'", resource.getName()); - try { - final ConnectorFacadeProxy connector = connInstanceLoader.getConnector(resource); + final Map<String, ConnectorObject> externalResources = new HashMap<String, ConnectorObject>(); - final SchemaMappingUtil.SchemaMappingsWrapper mappings = new SchemaMappingUtil.SchemaMappingsWrapper( - resource.getMappings()); + // ----------------------- + // Retrieve virtual attribute values + // ----------------------- + for (AbstractVirAttr virAttr : owner.getVirtualAttributes()) { + final String schemaName = virAttr.getVirtualSchema().getName(); + final List<String> values = virAttrCache.get(owner.getId(), schemaName); - final String accountId = SchemaMappingUtil.getAccountIdValue(owner, mappings.getAccountIdMapping()); + LOG.debug("Retrieve values for virtual attribute {}", schemaName); - LOG.debug("Search for object with accountId '{}'", accountId); + if (values == null) { + // non cached ... + LOG.debug("Need one or more remote connections"); + for (ExternalResource resource : getTargetResource(virAttr, type)) { + LOG.debug("Seach values into {}", resource.getName()); + try { + final ConnectorObject connectorObject; + + if (externalResources.containsKey(resource.getName())) { + connectorObject = externalResources.get(resource.getName()); + } else { + LOG.debug("Perform connection to {}", resource.getName()); + final String accountId = SchemaMappingUtil.getAccountIdValue( + owner, SchemaMappingUtil.getAccountIdMapping(resource.getMappings())); - if (StringUtils.isNotBlank(accountId)) { - // Retrieve attributes to get - final Set<String> extAttrNames = new HashSet<String>(); + if (StringUtils.isBlank(accountId)) { + throw new IllegalArgumentException("No AccountId found for " + resource.getName()); + } - for (Collection<SchemaMapping> virAttrMappings : mappings.getuVirMappings().values()) { - for (SchemaMapping virAttrMapping : virAttrMappings) { - extAttrNames.add(SchemaMappingUtil.getExtAttrName(virAttrMapping)); - } - } + final Set<String> extAttrNames = + SchemaMappingUtil.getExtAttrNames(resource.getMappings(), type); - // Search for remote object - if (extAttrNames != null) { - final OperationOptionsBuilder oob = new OperationOptionsBuilder(); - oob.setAttributesToGet(extAttrNames); + LOG.debug("External attribute ({}) names to get '{}'", type, extAttrNames); - final ConnectorObject connectorObject = connector.getObject(ObjectClass.ACCOUNT, new Uid( - accountId), oob.build()); + final OperationOptionsBuilder oob = new OperationOptionsBuilder(); + oob.setAttributesToGet(extAttrNames); - if (connectorObject != null) { - remoteObjects.put(mappings, connectorObject); + final ConnectorFacadeProxy connector = connInstanceLoader.getConnector(resource); + connectorObject = connector.getObject(ObjectClass.ACCOUNT, new Uid(accountId), oob.build()); + externalResources.put(resource.getName(), connectorObject); } - LOG.debug("Retrieved remotye object {}", connectorObject); - } - } - } catch (Exception e) { - LOG.error("Unable to retrieve virtual attribute values on '{}'", resource.getName(), e); - } - } - for (AbstractVirAttr virAttr : owner.getVirtualAttributes()) { - LOG.debug("Provide value for virtual attribute '{}'", virAttr.getVirtualSchema().getName()); + final Set<SchemaMapping> mappings = resource.getMappings(schemaName, type); - for (SchemaMappingUtil.SchemaMappingsWrapper mappings : remoteObjects.keySet()) { - Collection<SchemaMapping> virAttrMappings = mappings.getuVirMappings().get( - virAttr.getVirtualSchema().getName()); - - if (virAttrMappings != null) { - for (SchemaMapping virAttrMapping : virAttrMappings) { - String extAttrName = SchemaMappingUtil.getExtAttrName(virAttrMapping); - Attribute extAttr = remoteObjects.get(mappings).getAttributeByName(extAttrName); - - if (extAttr != null && extAttr.getValue() != null && !extAttr.getValue().isEmpty()) { - for (Object obj : extAttr.getValue()) { - if (obj != null) { - virAttr.addValue(obj.toString()); + // the same virtual attribute could be mapped with one or more external attribute + for (SchemaMapping mapping : mappings) { + final Attribute attribute = + connectorObject.getAttributeByName(SchemaMappingUtil.getExtAttrName(mapping)); + + if (attribute != null && attribute.getValue() != null) { + for (Object obj : attribute.getValue()) { + if (obj != null) { + virAttr.addValue(obj.toString()); + } } } } + + LOG.debug("Retrieved values {}", virAttr.getValues()); + } catch (Exception e) { + LOG.error("Error reading connector object from {}", resource.getName(), e); } } + + virAttrCache.put(owner.getId(), schemaName, virAttr.getValues()); + } else { + // cached ... + LOG.debug("Values found in cache {}", values); + virAttr.setValues(values); + } + } + // ----------------------- + } + + private Set<ExternalResource> getTargetResource(final AbstractVirAttr attr, final IntMappingType type) { + + final Set<ExternalResource> resources = new HashSet<ExternalResource>(); + + for (ExternalResource res : attr.getOwner().getResources()) { + if (!SchemaMappingUtil.getMappings(res.getMappings(), attr.getVirtualSchema().getName(), type).isEmpty()) { + resources.add(res); } } - LOG.debug("Virtual attribute evaluation ended"); + return resources; } private void fillFromTemplate(final AbstractAttributableTO attributableTO, final AbstractAttributableTO template) { Modified: syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/util/SchemaMappingUtil.java URL: http://svn.apache.org/viewvc/syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/util/SchemaMappingUtil.java?rev=1445543&r1=1445542&r2=1445543&view=diff ============================================================================== --- syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/util/SchemaMappingUtil.java (original) +++ syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/util/SchemaMappingUtil.java Wed Feb 13 10:26:16 2013 @@ -57,6 +57,18 @@ public class SchemaMappingUtil { */ private static final Logger LOG = LoggerFactory.getLogger(SchemaMappingUtil.class); + public static Set<String> getExtAttrNames(final Collection<SchemaMapping> mappings, final IntMappingType type) { + final Set<String> res = new HashSet<String>(); + + for (SchemaMapping mapping : mappings) { + if (mapping.getIntMappingType() == type) { + res.add(getExtAttrName(mapping)); + } + } + + return res; + } + public static String getExtAttrName(final SchemaMapping mapping) { final String name; Added: syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/util/VirAttrCache.java URL: http://svn.apache.org/viewvc/syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/util/VirAttrCache.java?rev=1445543&view=auto ============================================================================== --- syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/util/VirAttrCache.java (added) +++ syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/util/VirAttrCache.java Wed Feb 13 10:26:16 2013 @@ -0,0 +1,159 @@ +/* + * 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.util; + +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +/** + * Virtual Attribute Value cache. + */ +public final class VirAttrCache { + + /** + * Elapsed time in seconds. + */ + private final int ttl; + + /** + * Max cache size. + */ + private final int maxCacheSize; + + /** + * Clean period. + */ + private final int cleanPeriod; + + /** + * Cache entries. + */ + private final Map<VirAttrCacheKey, VirAttrCacheValue> cache = new HashMap<VirAttrCacheKey, VirAttrCacheValue>(); + + public VirAttrCache(final int ttl, final int maxCacheSize, final int cleanPeriod) { + this.ttl = ttl; + this.maxCacheSize = maxCacheSize; + this.cleanPeriod = cleanPeriod; + + Executors.newScheduledThreadPool(1).scheduleWithFixedDelay( + new Runnable() { + + @Override + public void run() { + synchronized (cache) { + freeCacheSpace(false); + } + } + }, cleanPeriod, cleanPeriod, TimeUnit.MINUTES); + } + + /** + * Cache virtual attribute values. + * + * @param userId user id. + * @param schemaName virtual attribute name. + * @param values virtual attribute values. + */ + public void put(final Long userId, final String schemaName, final List<String> values) { + synchronized (cache) { + // this operations (retrieve cache space and put entry on) have to be thread safe. + + if (cache.size() >= maxCacheSize) { + freeCacheSpace(true); + } + + cache.put(new VirAttrCacheKey(userId, schemaName), new VirAttrCacheValue(values)); + } + } + + /** + * Retrieve cached value. Return null in case of virtual attribute not cached. + * + * @param userId user id. + * @param schemaName virtual attribute schema name. + * @return cached values or null in case of virtual attribute not found. + */ + public List<String> get(final Long userId, final String schemaName) { + final VirAttrCacheValue value = cache.get(new VirAttrCacheKey(userId, schemaName)); + return isValidEntry(value) ? value.getValues() : null; + } + + /** + * Force entry expiring. + * + * @param userId user id. + * @param schemaName virtual attribute schema name. + */ + public void expire(final Long userId, final String schemaName) { + final VirAttrCacheValue value = cache.get(new VirAttrCacheKey(userId, schemaName)); + if (isValidEntry(value)) { + synchronized (cache) { + value.forceExpiring(); + } + } + } + + /** + * Remove expired entries if exist. If required, one entry at least (the latest recently used) will be taken off. + * This method is not thread safe: the caller have to take care to synchronize the call. + * + * @param forceEscape if TRUE the latest recently used entry at least will be taken off. + */ + private void freeCacheSpace(final boolean forceEscape) { + final Set<VirAttrCacheKey> toBeRemoved = new HashSet<VirAttrCacheKey>(); + + Map.Entry<VirAttrCacheKey, VirAttrCacheValue> latest = null; + + for (Map.Entry<VirAttrCacheKey, VirAttrCacheValue> entry : cache.entrySet()) { + if (isValidEntry(entry.getValue())) { + final Date date = entry.getValue().getLastAccessDate(); + if (latest == null || latest.getValue().getLastAccessDate().after(date)) { + latest = entry; + } + } else { + toBeRemoved.add(entry.getKey()); + } + } + + if (toBeRemoved.isEmpty() && forceEscape) { + // remove the oldest entry. + cache.remove(latest.getKey()); + } else { + // remove expired entries. + cache.keySet().removeAll(toBeRemoved); + } + } + + /** + * Cache entry is valid if and only if value exist and it is not expired. + * + * @param value cache entry value. + * @return TRUE if the value is valid; FALSE otherwise. + */ + private boolean isValidEntry(final VirAttrCacheValue value) { + final Date expiringDate = new Date(value == null ? 0 : value.getCreationDate().getTime() + ttl * 1000); + return expiringDate.after(new Date()); + } +} Propchange: syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/util/VirAttrCache.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/util/VirAttrCache.java ------------------------------------------------------------------------------ svn:keywords = Date Author Id Revision HeadURL Propchange: syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/util/VirAttrCache.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/util/VirAttrCacheKey.java URL: http://svn.apache.org/viewvc/syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/util/VirAttrCacheKey.java?rev=1445543&view=auto ============================================================================== --- syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/util/VirAttrCacheKey.java (added) +++ syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/util/VirAttrCacheKey.java Wed Feb 13 10:26:16 2013 @@ -0,0 +1,64 @@ +/* + * 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.util; + +/** + * Ccahe entry key. + */ +public class VirAttrCacheKey { + + /** + * User ID. + */ + private final transient Long userId; + + /** + * Virtual attribute schema name. + */ + private final transient String virAttrSchema; + + public VirAttrCacheKey(final Long userId, final String virAttrSchema) { + this.userId = userId; + this.virAttrSchema = virAttrSchema; + } + + public Long getUserId() { + return userId; + } + + public String getVirAttrSchema() { + return virAttrSchema; + } + + @Override + public int hashCode() { + return 31 * (31 + + (userId == null ? 0 : userId.hashCode())) + + virAttrSchema == null ? 0 : virAttrSchema.hashCode(); + } + + @Override + public boolean equals(Object o) { + return o != null && o instanceof VirAttrCacheKey + && ((userId == null && ((VirAttrCacheKey) o).getUserId() == null) + || userId.equals(((VirAttrCacheKey) o).getUserId())) + && ((virAttrSchema == null && ((VirAttrCacheKey) o).getVirAttrSchema() == null) + || virAttrSchema.equals(((VirAttrCacheKey) o).getVirAttrSchema())); + } +} Propchange: syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/util/VirAttrCacheKey.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/util/VirAttrCacheKey.java ------------------------------------------------------------------------------ svn:keywords = Date Author Id Revision HeadURL Propchange: syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/util/VirAttrCacheKey.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/util/VirAttrCacheValue.java URL: http://svn.apache.org/viewvc/syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/util/VirAttrCacheValue.java?rev=1445543&view=auto ============================================================================== --- syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/util/VirAttrCacheValue.java (added) +++ syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/util/VirAttrCacheValue.java Wed Feb 13 10:26:16 2013 @@ -0,0 +1,69 @@ +/* + * 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.util; + +import java.util.Date; +import java.util.List; + +/** + * Cache entry value. + */ +public class VirAttrCacheValue { + + /** + * Virtual attribute values. + */ + private final List<String> values; + + /** + * Entry creation date. + */ + private Date creationDate; + + /** + * Entry access date. + */ + private Date lastAccessDate; + + public VirAttrCacheValue(final List<String> values) { + this.values = values; + this.creationDate = new Date(); + this.lastAccessDate = new Date(); + } + + public Date getCreationDate() { + return creationDate; + } + + public void forceExpiring() { + creationDate = new Date(0); + } + + public List<String> getValues() { + return values; + } + + public Date getLastAccessDate() { + return lastAccessDate; + } + + void setLastAccessDate(Date lastAccessDate) { + this.lastAccessDate = lastAccessDate; + } +} Propchange: syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/util/VirAttrCacheValue.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/util/VirAttrCacheValue.java ------------------------------------------------------------------------------ svn:keywords = Date Author Id Revision HeadURL Propchange: syncope/branches/1_0_X/core/src/main/java/org/apache/syncope/core/util/VirAttrCacheValue.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Modified: syncope/branches/1_0_X/core/src/main/resources/syncopeContext.xml URL: http://svn.apache.org/viewvc/syncope/branches/1_0_X/core/src/main/resources/syncopeContext.xml?rev=1445543&r1=1445542&r2=1445543&view=diff ============================================================================== --- syncope/branches/1_0_X/core/src/main/resources/syncopeContext.xml (original) +++ syncope/branches/1_0_X/core/src/main/resources/syncopeContext.xml Wed Feb 13 10:26:16 2013 @@ -84,5 +84,12 @@ under the License. <property name="lenient" value="true"/> <property name="silent" value="false"/> </bean> + <bean id="jexlUtil" class="org.apache.syncope.core.util.JexlUtil"/> + + <bean id="virAttrCache" class="org.apache.syncope.core.util.VirAttrCache" scope="singleton"> + <constructor-arg value="60"/> + <constructor-arg value="5000"/> + <constructor-arg value="5"/> + </bean> </beans> Modified: syncope/branches/1_0_X/core/src/test/java/org/apache/syncope/core/rest/UserTestITCase.java URL: http://svn.apache.org/viewvc/syncope/branches/1_0_X/core/src/test/java/org/apache/syncope/core/rest/UserTestITCase.java?rev=1445543&r1=1445542&r2=1445543&view=diff ============================================================================== --- syncope/branches/1_0_X/core/src/test/java/org/apache/syncope/core/rest/UserTestITCase.java (original) +++ syncope/branches/1_0_X/core/src/test/java/org/apache/syncope/core/rest/UserTestITCase.java Wed Feb 13 10:26:16 2013 @@ -2216,4 +2216,69 @@ public class UserTestITCase extends Abst userTO.addResource("ws-target-resource-3"); restTemplate.postForObject(BASE_URL + "user/create", userTO, UserTO.class); } + + @Test + public void virAttrCache() { + UserTO userTO = getSampleTO("virattrca...@apache.org"); + userTO.getVirtualAttributes().clear(); + + AttributeTO virAttrTO = new AttributeTO(); + virAttrTO.setSchema("virtualdata"); + virAttrTO.addValue("virattrcache"); + userTO.addVirtualAttribute(virAttrTO); + + userTO.getMemberships().clear(); + userTO.getResources().clear(); + userTO.addResource("resource-db-virattr"); + + // 1. create user + UserTO actual = restTemplate.postForObject(BASE_URL + "user/create", userTO, UserTO.class); + assertNotNull(actual); + + // 2. check for virtual attribute value + actual = restTemplate.getForObject(BASE_URL + "user/read/{userId}.json", UserTO.class, actual.getId()); + assertEquals("virattrcache", actual.getVirtualAttributeMap().get("virtualdata").getValues().get(0)); + + Exception exception = null; + try { + final JdbcTemplate jdbcTemplate = new JdbcTemplate(testDataSource); + + String value = jdbcTemplate.queryForObject( + "SELECT USERNAME FROM testsync WHERE ID=?", String.class, actual.getId()); + assertEquals("virattrcache", value); + + jdbcTemplate.update("UPDATE testsync set USERNAME='virattrcache2' WHERE ID=?", userTO.getId()); + + value = jdbcTemplate.queryForObject( + "SELECT USERNAME FROM testsync WHERE ID=?", String.class, userTO.getId()); + assertEquals("virattrcache2", value); + + } catch (EmptyResultDataAccessException e) { + exception = e; + } + assertNotNull(exception); + + // 2. check for cached attribute value + actual = restTemplate.getForObject(BASE_URL + "user/read/{userId}.json", UserTO.class, actual.getId()); + assertEquals("virattrcache", actual.getVirtualAttributeMap().get("virtualdata").getValues().get(0)); + + UserMod userMod = new UserMod(); + userMod.setId(actual.getId()); + + AttributeMod virtualdata = new AttributeMod(); + virtualdata.setSchema("virtualdata"); + virtualdata.addValueToBeAdded("virtualupdated"); + + userMod.addVirtualAttributeToBeRemoved("virtualdata"); + userMod.addVirtualAttributeToBeUpdated(virtualdata); + + // 3. update virtual attribute + actual = restTemplate.postForObject(BASE_URL + "user/update", userMod, UserTO.class); + assertNotNull(actual); + + // 4. check for virtual attribute value + actual = restTemplate.getForObject(BASE_URL + "user/read/{userId}.json", UserTO.class, actual.getId()); + assertNotNull(actual); + assertEquals("virtualupdated", actual.getVirtualAttributeMap().get("virtualdata").getValues().get(0)); + } }