/* * * * Copyright (c) 2016. David Sowerby * * * * 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 uk.q3c.krail.core.persist.cache.i18n; import com.google.common.cache.CacheLoader; import com.google.inject.Inject; import com.vaadin.data.Property; import uk.q3c.krail.core.i18n.*; import uk.q3c.krail.core.option.Option; import uk.q3c.krail.core.option.OptionContext; import uk.q3c.krail.core.option.OptionKey; import uk.q3c.krail.core.persist.common.i18n.PatternDao; import javax.annotation.Nonnull; import java.lang.annotation.Annotation; import java.util.List; import java.util.Locale; import java.util.Optional; import static com.google.common.base.Preconditions.checkNotNull; /** * Loads the cache from potentially multiple sources by calling each of the DAOs in turn to provide a pattern. * Rewritten to use Annotations as source identifiers, and {@link PatternDao} in place of bundle readers. Much of the functionality delegated to {@link * PatternSourceProvider}. David Sowerby 26/07/15 * Created by David Sowerby on 08/12/14. */ public class DefaultPatternCacheLoader extends CacheLoader<PatternCacheKey, String> implements PatternCacheLoader, OptionContext { public static final OptionKey<Boolean> optionKeyAutoStub = new OptionKey<>(Boolean.FALSE, DefaultPatternSourceProvider.class, LabelKey.Auto_Stub, DescriptionKey.Auto_Stub); public static final OptionKey<Boolean> optionKeyStubWithKeyName = new OptionKey<>(Boolean.TRUE, DefaultPatternSourceProvider.class, LabelKey .Stub_with_Key_Name, DescriptionKey.Stub_with_Key_Name); public static final OptionKey<String> optionKeyStubValue = new OptionKey<>("undefined", DefaultPatternSourceProvider.class, LabelKey.Stub_Value, DescriptionKey.Stub_Value); private Option option; private PatternSourceProvider sourceProvider; @Inject public DefaultPatternCacheLoader(PatternSourceProvider sourceProvider, Option option) { this.sourceProvider = sourceProvider; this.option = option; } /** * Retrieves the value corresponding to {@code key}. The required Locale (from the {@code cacheKey}) is checked for each source in turn, and if that * fails to provide a result then the next candidate Locale is used, and each source tried again. If all candidate locales, for all sources, are * exhausted and still no pattern is found, then the name of the key is returned. * <p> * if a value is found the {@link PatternCacheKey#actualLocale} is set to the Locale the value was found for. This means that after this method is called * and a value if found, the {@link PatternCacheKey#requestedLocale} contains the Locale originally requested, and {@link PatternCacheKey#actualLocale} * contains the Locale a value was found for. However, if no value is found, and the key name is returned, then {@link PatternCacheKey#actualLocale} is * still set to the requestedLocale, but {@link PatternCacheKey#source} will be null * <p> * The order that sources are accessed is determined by {@link PatternSourceProvider#orderedSources(I18NKey)}, which in turn is configured in the {@link * I18NModule} * <p> * The native Java method for identifying candidate locales is used - see ResourceBundle.Control .getCandidateLocales * * @param cacheKey the non-null key whose value should be loaded * @return the value associated with {@code key}; <b>must not be null</b> * @throws Exception if unable to load the result * @throws InterruptedException if this method is interrupted. {@code InterruptedException} is * treated like any other {@code Exception} in all respects except that, when it is caught, * the thread's interrupt status is set */ @Override public String load(@Nonnull PatternCacheKey cacheKey) throws Exception { checkNotNull(cacheKey); I18NKey i18NKey = cacheKey.getKey(); // Use standard Java call to get candidates KrailResourceBundleControl bundleControl = new KrailResourceBundleControl(); List<Locale> candidateLocales = bundleControl.getCandidateLocales(i18NKey.bundleName(), cacheKey.getRequestedLocale()); Optional<String> value = Optional.empty(); for (Locale candidateLocale : candidateLocales) { //try each source in turn for a valid pattern for (Class<? extends Annotation> source : sourceProvider.orderedSources(i18NKey)) { cacheKey.setSource(source); cacheKey.setActualLocale(candidateLocale);// used to look up the bundle //get the Dao - we don't need to check that it is present, as we are using sources from sourceProvider PatternDao dao = sourceProvider.sourceFor(source) .get(); //get value from dao, break out if present value = dao.getValue(cacheKey); if (value.isPresent()) { break; } //value is not present, auto-stub if required // auto-stubbing if required Boolean autoStub = option.get(optionKeyAutoStub.qualifiedWith(source.getSimpleName())); /* autosSub to the selected target */ if (autoStub) { sourceProvider.selectedTargets() .getList() .forEach(t -> { Optional<PatternDao> target = sourceProvider.targetFor(t); if (target.isPresent()) { target.get() .write(cacheKey, stubValue(source, cacheKey)); } }); } } if (value.isPresent()) { cacheKey.setActualLocale(candidateLocale); break; } } if (!value.isPresent()) { value = Optional.of(cacheKey.getKeyAsEnum() .name() .replace('_', ' ')); cacheKey.setSource(null); } return value.get(); } /** * When auto-stubbing the value used can either be the key name or a value specified by {@link #optionKeyStubValue} * * @param source the pattern source * @param cacheKey the key to identify the entry * @return the value to assign to the key */ protected String stubValue(Class<? extends Annotation> source, PatternCacheKey cacheKey) { Boolean stubWithKeyName = option.get(optionKeyStubWithKeyName.qualifiedWith(source.getSimpleName())); return (stubWithKeyName) ? cacheKey.getKeyAsEnum() .name() : option.get(optionKeyStubValue.qualifiedWith(source.getSimpleName())); } /** * {@inheritDoc} */ @Nonnull @Override public Option getOption() { return option; } /** * {@inheritDoc} */ @Override public void optionValueChanged(Property.ValueChangeEvent event) { // do nothing, Option called as needed } }