/*
* 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 org.apache.shindig.common.cache.Cache;
import org.apache.shindig.common.cache.CacheProvider;
import org.apache.shindig.common.cache.SoftExpiringCache;
import org.apache.shindig.common.cache.SoftExpiringCache.CachedObject;
import org.apache.shindig.common.uri.Uri;
import org.apache.shindig.gadgets.http.HttpFetcher;
import org.apache.shindig.gadgets.http.HttpRequest;
import org.apache.shindig.gadgets.http.HttpResponse;
import org.apache.shindig.gadgets.spec.GadgetSpec;
import org.apache.shindig.gadgets.spec.LocaleSpec;
import org.apache.shindig.gadgets.spec.MessageBundle;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
import java.util.Locale;
import java.util.logging.Logger;
/**
* Default implementation of a message bundle factory.
*
* Containers wishing to implement custom bundle fetching behavior should override
* {@link #fetchBundle}.
*/
@Singleton
public class DefaultMessageBundleFactory implements MessageBundleFactory {
private static final Locale ALL_ALL = new Locale("all", "ALL");
public static final String CACHE_NAME = "messageBundles";
static final Logger LOG = Logger.getLogger(DefaultMessageBundleFactory.class.getName());
private final HttpFetcher fetcher;
final SoftExpiringCache<String, MessageBundle> cache;
private final long refresh;
@Inject
public DefaultMessageBundleFactory(HttpFetcher fetcher,
CacheProvider cacheProvider,
@Named("shindig.cache.xml.refreshInterval") long refresh) {
this.fetcher = fetcher;
Cache<String, MessageBundle> baseCache = cacheProvider.createCache(CACHE_NAME);
this.cache = new SoftExpiringCache<String, MessageBundle>(baseCache);
this.refresh = refresh;
}
public MessageBundle getBundle(GadgetSpec spec, Locale locale, boolean ignoreCache)
throws GadgetException {
if (ignoreCache) {
return getNestedBundle(spec, locale, true);
}
String key = spec.getUrl().toString() + '.' + locale.toString();
CachedObject<MessageBundle> cached = cache.getElement(key);
MessageBundle bundle;
if (cached == null || cached.isExpired) {
try {
bundle = getNestedBundle(spec, locale, ignoreCache);
} catch (GadgetException e) {
// Enforce negative caching.
if (cached != null) {
LOG.info("MessageBundle fetch failed for " + key + " - using cached.");
bundle = cached.obj;
} else {
// We create this dummy spec to avoid the cost of re-parsing when a remote site is out.
LOG.info("MessageBundle fetch failed for " + key + " - using default.");
bundle = MessageBundle.EMPTY;
}
}
cache.addElement(key, bundle, refresh);
} else {
bundle = cached.obj;
}
return bundle;
}
private MessageBundle getNestedBundle(GadgetSpec spec, Locale locale, boolean ignoreCache)
throws GadgetException {
MessageBundle parent = getParentBundle(spec, locale, ignoreCache);
MessageBundle child = null;
LocaleSpec localeSpec = spec.getModulePrefs().getLocale(locale);
if (localeSpec == null) {
return parent == null ? MessageBundle.EMPTY : parent;
}
Uri messages = localeSpec.getMessages();
if (messages == null || messages.toString().length() == 0) {
child = localeSpec.getMessageBundle();
} else {
child = fetchBundle(localeSpec, ignoreCache);
}
return new MessageBundle(parent, child);
}
private MessageBundle getParentBundle(GadgetSpec spec, Locale locale, boolean ignoreCache)
throws GadgetException {
if (locale.getLanguage().equalsIgnoreCase("all")) {
// Top most locale already.
return null;
}
if (locale.getCountry().equalsIgnoreCase("ALL")) {
return getBundle(spec, ALL_ALL, ignoreCache);
}
return getBundle(spec, new Locale(locale.getLanguage(), "ALL"), ignoreCache);
}
protected MessageBundle fetchBundle(LocaleSpec locale, boolean ignoreCache)
throws GadgetException {
Uri url = locale.getMessages();
HttpRequest request = new HttpRequest(url).setIgnoreCache(ignoreCache);
// Since we don't allow any variance in cache time, we should just force the cache time
// globally. This ensures propagation to shared caches when this is set.
request.setCacheTtl((int) (refresh / 1000));
HttpResponse response = fetcher.fetch(request);
if (response.getHttpStatusCode() != HttpResponse.SC_OK) {
throw new GadgetException(GadgetException.Code.FAILED_TO_RETRIEVE_CONTENT,
"Unable to retrieve message bundle xml. HTTP error " +
response.getHttpStatusCode());
}
MessageBundle bundle = new MessageBundle(locale, response.getResponseAsString());
return bundle;
}
}