/*
* 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.shindig.gadgets;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.junit.Assert.assertEquals;
import org.apache.shindig.common.cache.CacheProvider;
import org.apache.shindig.common.cache.LruCacheProvider;
import org.apache.shindig.common.cache.SoftExpiringCache;
import org.apache.shindig.common.testing.TestExecutorService;
import org.apache.shindig.common.uri.Uri;
import org.apache.shindig.config.ContainerConfig;
import org.apache.shindig.gadgets.http.HttpRequest;
import org.apache.shindig.gadgets.http.HttpResponse;
import org.apache.shindig.gadgets.http.HttpResponseBuilder;
import org.apache.shindig.gadgets.http.RequestPipeline;
import org.apache.shindig.gadgets.spec.GadgetSpec;
import org.apache.shindig.gadgets.spec.SpecParserException;
import org.easymock.EasyMock;
import org.junit.Test;
/**
* Tests for DefaultGadgetSpecFactory
*/
public class DefaultGadgetSpecFactoryTest {
private static final Uri SPEC_URL = Uri.parse("http://example.org/gadget.xml");
private static final String LOCAL_CONTENT = "Hello, local content!";
private static final String ALT_LOCAL_CONTENT = "Hello, local content!";
private static final String RAWXML_CONTENT = "Hello, rawxml content!";
private static final String LOCAL_SPEC_XML
= "<Module>" +
" <ModulePrefs title='GadgetSpecFactoryTest'/>" +
" <Content type='html'>" + LOCAL_CONTENT + "</Content>" +
"</Module>";
private static final String ALT_LOCAL_SPEC_XML
= "<Module>" +
" <ModulePrefs title='GadgetSpecFactoryTest'/>" +
" <Content type='html'>" + ALT_LOCAL_CONTENT + "</Content>" +
"</Module>";
private static final String RAWXML_SPEC_XML
= "<Module>" +
" <ModulePrefs title='GadgetSpecFactoryTest'/>" +
" <Content type='html'>" + RAWXML_CONTENT + "</Content>" +
"</Module>";
private static final GadgetContext RAWXML_GADGET_CONTEXT = new GadgetContext() {
@Override
public boolean getIgnoreCache() {
// This should be ignored by calling code.
return false;
}
@Override
public Uri getUrl() {
return SPEC_URL;
}
@Override
public String getParameter(String param) {
if (param.equals(DefaultGadgetSpecFactory.RAW_GADGETSPEC_XML_PARAM_NAME)) {
return RAWXML_SPEC_XML;
}
return null;
}
};
private static final int MAX_AGE = 10000;
private final CountingExecutor executor = new CountingExecutor();
private final RequestPipeline pipeline = EasyMock.createNiceMock(RequestPipeline.class);
private final CacheProvider cacheProvider = new LruCacheProvider(5);
private final DefaultGadgetSpecFactory specFactory
= new DefaultGadgetSpecFactory(executor, pipeline, cacheProvider, MAX_AGE);
private static HttpRequest createIgnoreCacheRequest() {
return new HttpRequest(SPEC_URL)
.setIgnoreCache(true)
.setGadget(SPEC_URL)
.setContainer(ContainerConfig.DEFAULT_CONTAINER);
}
private static HttpRequest createCacheableRequest() {
return new HttpRequest(SPEC_URL)
.setGadget(SPEC_URL)
.setContainer(ContainerConfig.DEFAULT_CONTAINER);
}
private static GadgetContext createContext(final Uri uri, final boolean ignoreCache) {
return new GadgetContext() {
@Override
public Uri getUrl() {
return uri;
}
@Override
public boolean getIgnoreCache() {
return ignoreCache;
}
};
}
@Test
public void specFetched() throws Exception {
HttpRequest request = createIgnoreCacheRequest();
HttpResponse response = new HttpResponse(LOCAL_SPEC_XML);
expect(pipeline.execute(request)).andReturn(response);
replay(pipeline);
GadgetSpec spec = specFactory.getGadgetSpec(createContext(SPEC_URL, true));
assertEquals(LOCAL_CONTENT, spec.getView(GadgetSpec.DEFAULT_VIEW).getContent());
}
@Test
public void specFetchedWithBom() throws Exception {
HttpRequest request = createIgnoreCacheRequest();
HttpResponse response = new HttpResponse("" + LOCAL_SPEC_XML);
expect(pipeline.execute(request)).andReturn(response);
replay(pipeline);
GadgetSpec spec = specFactory.getGadgetSpec(createContext(SPEC_URL, true));
assertEquals(LOCAL_CONTENT, spec.getView(GadgetSpec.DEFAULT_VIEW).getContent());
}
@Test(expected = GadgetException.class)
public void specFetchedEmptyContent() throws Exception {
HttpRequest request = createIgnoreCacheRequest();
HttpResponse response = new HttpResponse("");
expect(pipeline.execute(request)).andReturn(response);
replay(pipeline);
specFactory.getGadgetSpec(createContext(SPEC_URL, true));
}
@Test(expected = GadgetException.class)
public void malformedGadgetSpecIsCachedAndThrows2() throws Exception {
HttpRequest request = createIgnoreCacheRequest();
expect(pipeline.execute(request)).andReturn(new HttpResponse("")).once();
replay(pipeline);
specFactory.getGadgetSpec(createContext(SPEC_URL, true));
}
@Test
public void specFetchedWithBomChar() throws Exception {
HttpRequest request = createIgnoreCacheRequest();
HttpResponse response = new HttpResponse('\uFEFF' + LOCAL_SPEC_XML);
expect(pipeline.execute(request)).andReturn(response);
replay(pipeline);
GadgetSpec spec = specFactory.getGadgetSpec(createContext(SPEC_URL, true));
assertEquals(LOCAL_CONTENT, spec.getView(GadgetSpec.DEFAULT_VIEW).getContent());
}
// TODO: Move these tests into AbstractSpecFactoryTest
@Test
public void specRefetchedAsync() throws Exception {
HttpRequest request = createCacheableRequest();
HttpResponse response = new HttpResponse(ALT_LOCAL_SPEC_XML);
expect(pipeline.execute(request)).andReturn(response);
replay(pipeline);
specFactory.cache.addElement(
SPEC_URL, new GadgetSpec(SPEC_URL, LOCAL_SPEC_XML), -1);
GadgetSpec spec = specFactory.getGadgetSpec(createContext(SPEC_URL, false));
assertEquals(LOCAL_CONTENT, spec.getView(GadgetSpec.DEFAULT_VIEW).getContent());
spec = specFactory.getGadgetSpec(createContext(SPEC_URL, false));
assertEquals(ALT_LOCAL_CONTENT, spec.getView(GadgetSpec.DEFAULT_VIEW).getContent());
assertEquals(1, executor.runnableCount);
}
@Test
public void specFetchedFromParam() throws Exception {
// Set up request as if it's a regular spec request, and ensure that
// the return value comes from rawxml, not the pipeline.
HttpRequest request = createIgnoreCacheRequest();
HttpResponse response = new HttpResponse(LOCAL_SPEC_XML);
expect(pipeline.execute(request)).andReturn(response);
replay(pipeline);
GadgetSpec spec = specFactory.getGadgetSpec(RAWXML_GADGET_CONTEXT);
assertEquals(RAWXML_CONTENT, spec.getView(GadgetSpec.DEFAULT_VIEW).getContent());
assertEquals(DefaultGadgetSpecFactory.RAW_GADGET_URI, spec.getUrl());
}
@Test
public void staleSpecIsRefetched() throws Exception {
HttpRequest request = createIgnoreCacheRequest();
HttpRequest retriedRequest = createCacheableRequest();
HttpResponse expiredResponse = new HttpResponseBuilder()
.addHeader("Pragma", "no-cache")
.setResponse(LOCAL_SPEC_XML.getBytes("UTF-8"))
.create();
HttpResponse updatedResponse = new HttpResponse(ALT_LOCAL_SPEC_XML);
expect(pipeline.execute(request)).andReturn(expiredResponse).once();
expect(pipeline.execute(retriedRequest)).andReturn(updatedResponse).once();
replay(pipeline);
specFactory.getGadgetSpec(createContext(SPEC_URL, true));
SoftExpiringCache.CachedObject<Object> inCache = specFactory.cache.getElement(SPEC_URL);
specFactory.cache.addElement(SPEC_URL, inCache.obj, -1);
GadgetSpec spec = specFactory.getGadgetSpec(createContext(SPEC_URL, false));
assertEquals(ALT_LOCAL_CONTENT, spec.getView(GadgetSpec.DEFAULT_VIEW).getContent());
}
@Test
public void staleSpecReturnedFromCacheOnError() throws Exception {
HttpRequest request = createIgnoreCacheRequest();
HttpRequest retriedRequest = createCacheableRequest();
HttpResponse expiredResponse = new HttpResponseBuilder()
.setResponse(LOCAL_SPEC_XML.getBytes("UTF-8"))
.addHeader("Pragma", "no-cache")
.create();
expect(pipeline.execute(request)).andReturn(expiredResponse);
expect(pipeline.execute(retriedRequest)).andReturn(HttpResponse.notFound()).once();
replay(pipeline);
specFactory.getGadgetSpec(createContext(SPEC_URL, true));
SoftExpiringCache.CachedObject<Object> inCache = specFactory.cache.getElement(SPEC_URL);
specFactory.cache.addElement(SPEC_URL, inCache.obj, -1);
GadgetSpec spec = specFactory.getGadgetSpec(createContext(SPEC_URL, false));
assertEquals(ALT_LOCAL_CONTENT, spec.getView(GadgetSpec.DEFAULT_VIEW).getContent());
}
@Test
public void ttlPropagatesToPipeline() throws Exception {
CapturingPipeline capturingPipeline = new CapturingPipeline();
GadgetSpecFactory forcedCacheFactory = new DefaultGadgetSpecFactory(
new TestExecutorService(), capturingPipeline, cacheProvider, 10000);
forcedCacheFactory.getGadgetSpec(createContext(SPEC_URL, false));
assertEquals(10, capturingPipeline.request.getCacheTtl());
}
@Test(expected = GadgetException.class)
public void badFetchThrows() throws Exception {
HttpRequest request = createIgnoreCacheRequest();
expect(pipeline.execute(request)).andReturn(HttpResponse.error());
replay(pipeline);
specFactory.getGadgetSpec(createContext(SPEC_URL, true));
}
public void badFetchThrowsExceptionOverridingCache() throws Exception {
HttpRequest firstRequest = createCacheableRequest();
expect(pipeline.execute(firstRequest)).andReturn(new HttpResponse(LOCAL_SPEC_XML)).times(2);
HttpRequest secondRequest = createIgnoreCacheRequest();
expect(pipeline.execute(secondRequest)).andReturn(HttpResponse.error()).once();
replay(pipeline);
specFactory.getGadgetSpec(createContext(SPEC_URL, false));
try {
specFactory.getGadgetSpec(createContext(SPEC_URL, true));
} catch (GadgetException e) {
// Expected condition.
}
// Now make sure the cache wasn't populated w/ the error.
specFactory.getGadgetSpec(createContext(SPEC_URL, false));
}
@Test(expected = GadgetException.class)
public void malformedGadgetSpecThrows() throws Exception {
HttpRequest request = createIgnoreCacheRequest();
expect(pipeline.execute(request)).andReturn(new HttpResponse("malformed junk"));
replay(pipeline);
specFactory.getGadgetSpec(createContext(SPEC_URL, true));
}
@Test(expected = GadgetException.class)
public void malformedGadgetSpecIsCachedAndThrows() throws Exception {
HttpRequest request = createCacheableRequest();
expect(pipeline.execute(request)).andReturn(new HttpResponse("malformed junk")).once();
replay(pipeline);
specFactory.getGadgetSpec(createContext(SPEC_URL, false));
}
@Test(expected = GadgetException.class)
public void throwingPipelineRethrows() throws Exception {
HttpRequest request = createIgnoreCacheRequest();
expect(pipeline.execute(request)).andThrow(
new GadgetException(GadgetException.Code.FAILED_TO_RETRIEVE_CONTENT));
replay(pipeline);
specFactory.getGadgetSpec(createContext(SPEC_URL, true));
}
@Test(expected = SpecParserException.class)
public void negativeCachingEnforced() throws Exception {
specFactory.cache.addElement(SPEC_URL, new SpecParserException("broken"), 1000);
specFactory.getGadgetSpec(createContext(SPEC_URL, false));
}
private static class CountingExecutor extends TestExecutorService {
int runnableCount = 0;
@Override
public void execute(Runnable r) {
runnableCount++;
r.run();
}
}
private static class CapturingPipeline implements RequestPipeline {
HttpRequest request;
public HttpResponse execute(HttpRequest request) {
this.request = request;
return new HttpResponse(LOCAL_SPEC_XML);
}
}
}