/*
* Copyright (C) 2013 salesforce.com, inc.
*
* Licensed 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.auraframework.impl;
import java.io.StringWriter;
import java.lang.ref.WeakReference;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.SimpleLayout;
import org.apache.log4j.WriterAppender;
import org.apache.log4j.spi.LoggingEvent;
import org.auraframework.cache.Cache;
import org.auraframework.def.ApplicationDef;
import org.auraframework.def.ComponentDef;
import org.auraframework.def.DefDescriptor;
import org.auraframework.def.DefDescriptor.DefType;
import org.auraframework.def.Definition;
import org.auraframework.def.HelperDef;
import org.auraframework.def.StyleDef;
import org.auraframework.impl.system.DefDescriptorImpl;
import org.auraframework.service.CachingService;
import org.auraframework.system.DependencyEntry;
import org.auraframework.system.SourceListener;
import org.auraframework.system.SourceListener.SourceMonitorEvent;
import org.mockito.Mockito;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
public class CachingServiceImplTest extends AuraImplTestCase {
public CachingServiceImplTest(String name) {
super(name);
}
private class Log4jCaptureAppender extends AppenderSkeleton {
List<LoggingEvent> events;
public Log4jCaptureAppender(List<LoggingEvent> events) {
this.events = events;
}
@Override
public void close() {
}
@Override
public boolean requiresLayout() {
return false;
}
@Override
protected void append(LoggingEvent event) {
events.add(event);
}
}
public void testNotifyDependentSourceChange_LogsErrorIfWriteLockLocked() {
// capture logger output
StringWriter writer = new StringWriter();
Logger logger = Logger.getLogger(CachingServiceImpl.class);
logger.addAppender(new WriterAppender(new SimpleLayout(), writer));
List<LoggingEvent> events = Lists.newLinkedList();
logger.addAppender(new Log4jCaptureAppender(events));
// grab the lock
CachingServiceImpl service = new CachingServiceImpl();
Lock lock = service.getReadLock();
try {
lock.lock();
// try to notify
service.notifyDependentSourceChange(null, null, null, null);
long start = System.nanoTime();
do {
if (!events.isEmpty()) {
assertEquals("Unexpected number of events", 1,
events.size());
LoggingEvent event = events.get(0);
assertEquals("Unexpected logging level", Level.ERROR,
event.getLevel());
assertEquals(
"Couldn't acquire cache clear lock in a reasonable time. Cache may be stale until next clear.",
event.getMessage());
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
} while (System.nanoTime() - start < 10000000000L); // 10 secs from now
fail("Timed out waiting for error event due to unobtainable lock");
} finally {
lock.unlock();
}
}
public void testNotifyDependentSourceChange_NotifiesListeners() {
DefDescriptor<?> source = DefDescriptorImpl.getInstance(
getAuraTestingUtil().getNonce("some:descriptor"),
ComponentDef.class);
SourceMonitorEvent event = SourceMonitorEvent.CHANGED;
String filePath = "someFilePath";
Collection<WeakReference<SourceListener>> listeners = Sets.newHashSet();
for (int i = 0; i < 3; i++) {
listeners.add(new WeakReference<>(Mockito
.mock(SourceListener.class)));
}
CachingServiceImpl service = new CachingServiceImpl();
service.notifyDependentSourceChange(listeners, source, event, filePath);
for (WeakReference<SourceListener> ref : listeners) {
Mockito.verify(ref.get(), Mockito.times(1)).onSourceChanged(source,
event, filePath);
}
}
public void testNotifyDependentSourceChange_NotifiesNoListeners() {
DefDescriptor<?> source = DefDescriptorImpl.getInstance(
getAuraTestingUtil().getNonce("some:descriptor"),
ComponentDef.class);
SourceMonitorEvent event = SourceMonitorEvent.CHANGED;
String filePath = "someFilePath";
Collection<WeakReference<SourceListener>> listeners = Sets.newHashSet();
CachingServiceImpl service = new CachingServiceImpl();
service.notifyDependentSourceChange(listeners, source, event, filePath);
}
private <K, V> void testNotifyDependentSourceChange_InvalidatesSomeCachedValues(
CachingService service, Cache<K, V> cache,
Function<K, V> valGenerator, Set<K> keys, DefDescriptor<?> source,
Set<K> invalidatedKeys) {
// populate cache
for (K key : keys) {
cache.put(key, valGenerator.apply(key));
}
// check cache value existence
for (K key : keys) {
assertNotNull("Cache missing value for " + key,
cache.getIfPresent(key));
}
// call target function
service.notifyDependentSourceChange(
Collections.<WeakReference<SourceListener>> emptySet(), source,
null, null);
// check for invalidated and untouched keys
for (K key : keys) {
V val = cache.getIfPresent(key);
if (invalidatedKeys.contains(key)) {
assertNull("Cache not invalidated for " + key, val);
} else {
assertNotNull("Missing cached value for " + key, val);
}
}
}
private <K, V> void testNotifyDependentSourceChange_InvalidatesAllCachedValues(
CachingService service, Cache<K, V> cache,
Function<K, V> valGenerator, Set<K> keys) {
testNotifyDependentSourceChange_InvalidatesSomeCachedValues(service,
cache, valGenerator, keys, null, keys);
}
private Function<DefDescriptor<?>, Optional<? extends Definition>> mockDefinitionFunction = new Function<DefDescriptor<?>, Optional<? extends Definition>>() {
@Override
public Optional<? extends Definition> apply(DefDescriptor<?> key) {
return Optional.of(Mockito.mock(Definition.class));
}
};
public void testNotifyDependentSourceChange_InvalidatesAllCachedDependencies() {
Set<String> keys = Sets.newHashSet(
getAuraTestingUtil().getNonce("some:descriptor"),
getAuraTestingUtil().getNonce("other:descriptor"),
getAuraTestingUtil().getNonce("some:extra"));
CachingServiceImpl service = new CachingServiceImpl();
testNotifyDependentSourceChange_InvalidatesAllCachedValues(service,
service.getDepsCache(),
new Function<String, DependencyEntry>() {
@Override
public DependencyEntry apply(String key) {
return new DependencyEntry(null);
}
}, keys);
}
public void testNotifyDependentSourceChange_InvalidatesAllCachedDescriptorFilters() {
Set<String> keys = Sets.newHashSet(
getAuraTestingUtil().getNonce("some:descriptor"),
getAuraTestingUtil().getNonce("other:descriptor"),
getAuraTestingUtil().getNonce("some:extra"));
CachingServiceImpl service = new CachingServiceImpl();
testNotifyDependentSourceChange_InvalidatesAllCachedValues(service,
service.getDescriptorFilterCache(),
new Function<String, Set<DefDescriptor<?>>>() {
@Override
public Set<DefDescriptor<?>> apply(String key) {
return Collections.emptySet();
}
}, keys);
}
public void testNotifyDependentSourceChange_InvalidatesAllCachedStrings() {
Set<String> keys = Sets.newHashSet(
getAuraTestingUtil().getNonce("some:descriptor"),
getAuraTestingUtil().getNonce("other:descriptor"),
getAuraTestingUtil().getNonce("some:extra"));
CachingServiceImpl service = new CachingServiceImpl();
testNotifyDependentSourceChange_InvalidatesAllCachedValues(service,
service.getStringsCache(), new Function<String, String>() {
@Override
public String apply(String key) {
return "";
}
}, keys);
}
public void testNotifyDependentSourceChange_InvalidatesAllCachedDefinitionsIfDescriptorNull() {
Set<DefDescriptor<?>> keys = Sets.newHashSet();
keys.add(DefDescriptorImpl.getInstance(
getAuraTestingUtil().getNonce("some:descriptor"),
ComponentDef.class));
keys.add(DefDescriptorImpl.getInstance(
getAuraTestingUtil().getNonce("other:descriptor"),
ComponentDef.class));
keys.add(DefDescriptorImpl
.getInstance(getAuraTestingUtil().getNonce("some:extra"),
ComponentDef.class));
CachingServiceImpl service = new CachingServiceImpl();
testNotifyDependentSourceChange_InvalidatesAllCachedValues(service,
service.getDefsCache(), mockDefinitionFunction, keys);
}
private Set<DefDescriptor<?>> createDescriptors(DefDescriptor<?> baseDesc) {
Set<DefDescriptor<?>> res = Sets.newHashSet();
for (DefType defType : DefType.values()) {
// just use markup for these dummy descriptors
res.add(DefDescriptorImpl.getAssociateDescriptor(baseDesc,
defType.getPrimaryInterface(), DefDescriptor.MARKUP_PREFIX));
}
return res;
}
private <V> void testNotifyDependentSourceChange_InvalidatesSome(
CachingService service, Cache<DefDescriptor<?>, V> cache,
Function<DefDescriptor<?>, V> valGenerator,
Set<DefDescriptor<?>> baseDds, DefDescriptor<?> source,
Set<DefDescriptor<?>> invalidatedDds) {
Set<DefDescriptor<?>> dds = Sets.newHashSet();
for (DefDescriptor<?> baseDesc : baseDds) {
dds.addAll(createDescriptors(baseDesc));
}
testNotifyDependentSourceChange_InvalidatesSomeCachedValues(service,
cache, valGenerator, dds, source, invalidatedDds);
}
public void testNotifyDependentSourceChange_InvalidatesCachedSelfAndCmpDefinitionsIfDescriptorFound() {
DefDescriptor<?> source = DefDescriptorImpl.getInstance(
getAuraTestingUtil().getNonce("markup://some.desc"),
HelperDef.class);
Set<DefDescriptor<?>> baseDds = Sets.newHashSet();
baseDds.add(source);
baseDds.add(DefDescriptorImpl.getInstance(getAuraTestingUtil()
.getNonce("markup://some:else"), ComponentDef.class));
Set<DefDescriptor<?>> invalidatedDds = Sets.newHashSet();
invalidatedDds.add(source);
invalidatedDds.add(DefDescriptorImpl.getAssociateDescriptor(source,
ApplicationDef.class, DefDescriptor.MARKUP_PREFIX));
invalidatedDds.add(DefDescriptorImpl.getAssociateDescriptor(source,
ComponentDef.class, DefDescriptor.MARKUP_PREFIX));
CachingServiceImpl service = new CachingServiceImpl();
Cache<DefDescriptor<?>, Optional<? extends Definition>> cache = service
.getDefsCache();
testNotifyDependentSourceChange_InvalidatesSome(service, cache,
mockDefinitionFunction, baseDds, source, invalidatedDds);
}
public void testNotifyDependentSourceChange_InvalidatesCachedSelfAndCmpExistsIfDescriptorFound() {
DefDescriptor<?> source = DefDescriptorImpl.getInstance(
getAuraTestingUtil().getNonce("markup://some.desc"),
StyleDef.class);
Set<DefDescriptor<?>> baseDds = Sets.newHashSet();
baseDds.add(source);
baseDds.add(DefDescriptorImpl.getInstance(getAuraTestingUtil()
.getNonce("markup://some:else"), ComponentDef.class));
Set<DefDescriptor<?>> invalidatedDds = Sets.newHashSet();
invalidatedDds.add(source);
invalidatedDds.add(DefDescriptorImpl.getAssociateDescriptor(source,
ApplicationDef.class, DefDescriptor.MARKUP_PREFIX));
invalidatedDds.add(DefDescriptorImpl.getAssociateDescriptor(source,
ComponentDef.class, DefDescriptor.MARKUP_PREFIX));
CachingServiceImpl service = new CachingServiceImpl();
Cache<DefDescriptor<?>, Boolean> cache = service.getExistsCache();
testNotifyDependentSourceChange_InvalidatesSome(service, cache,
new Function<DefDescriptor<?>, Boolean>() {
@Override
public Boolean apply(DefDescriptor<?> input) {
return true;
}
}, baseDds, source, invalidatedDds);
}
public void testNotifyDependentSourceChange_InvalidatesAllCachedExistsIfDescriptorNull() {
Set<DefDescriptor<?>> keys = Sets.newHashSet();
keys.add(DefDescriptorImpl.getInstance(
getAuraTestingUtil().getNonce("some:descriptor"),
ComponentDef.class));
keys.add(DefDescriptorImpl.getInstance(
getAuraTestingUtil().getNonce("other:descriptor"),
ComponentDef.class));
keys.add(DefDescriptorImpl
.getInstance(getAuraTestingUtil().getNonce("some:extra"),
ComponentDef.class));
CachingServiceImpl service = new CachingServiceImpl();
testNotifyDependentSourceChange_InvalidatesAllCachedValues(service,
service.getExistsCache(),
new Function<DefDescriptor<?>, Boolean>() {
@Override
public Boolean apply(DefDescriptor<?> key) {
return true;
}
}, keys);
}
}