/* * Copyright 2008 FatWire Corporation. All Rights Reserved. * * 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 com.fatwire.gst.foundation.facade.mda; import COM.FutureTense.Interfaces.FTValList; import COM.FutureTense.Interfaces.ICS; import COM.FutureTense.Interfaces.IList; import COM.FutureTense.Util.IterableIListWrapper; import COM.FutureTense.Util.ftErrors; import com.fatwire.assetapi.data.AssetId; import com.fatwire.cs.core.db.PreparedStmt; import com.fatwire.cs.core.db.StatementParam; import com.fatwire.gst.foundation.CSRuntimeException; import com.fatwire.gst.foundation.IListUtils; import com.fatwire.gst.foundation.facade.runtag.asset.AssetLoadByName; import com.fatwire.gst.foundation.facade.runtag.render.LogDep; import com.fatwire.mda.Dimension; import com.fatwire.mda.DimensionException; import com.fatwire.mda.DimensionFilterInstance; import com.fatwire.mda.DimensionManager; import com.fatwire.mda.DimensionSetInstance; import com.fatwire.mda.DimensionableAssetManager; import com.fatwire.system.Session; import com.openmarket.xcelerate.asset.AssetIdImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; /** * Handles Locale-specific functions efficiently in Java. * * @author Tony Field * @since May 8, 2009 * @deprecated - com.fatwire.gst.foundation.facade and all subpackages have moved to the tools.gsf.facade package */ public final class LocaleUtils { private static final Logger _log = LoggerFactory.getLogger("tools.gsf.legacy.facade.mda.LocaleUtils"); private LocaleUtils() { } /** * Look up the translation for the asset specified, in the locale specified. * <p> * If the desired translation is not available, null will be returned. * <p> * If a dimension set for the site has been configured that returns the * asset other than the preferred locale, that is considered to be fine and * not really the problem of the end user. In other words, a dimension set * may dictate that a "backup" language can be returned to the user. * <p> * Null, however, is a valid option. * * @param c asset type of asset to look up * @param cid asset id of asset to look up * @param preferredLocaleDimensionId id of locale desired * @param site name of site * @param ics ics context * @return AssetId of translation asset. */ public static AssetId findTranslation(ICS ics, String c, String cid, String preferredLocaleDimensionId, String site) { return findTranslation(ics, new AssetIdImpl(c, Long.valueOf(cid)), preferredLocaleDimensionId, site); } /** * Look up the translation for the asset specified, in the locale specified. * <p> * If the desired translation is not available, null will be returned. * <p> * If a dimension set for the site has been configured that returns the * asset other than the preferred locale, that is considered to be fine and * not really the problem of the end user. In other words, a dimension set * may dictate that a "backup" language can be returned to the user. * <p> * Null, however, is a valid option. * * @param ics context * @param id id of asset to look up * @param preferredLocaleDimensionIdString id of locale desired * @param site name of site * @return AssetId of translation asset, or null if none is returned by the * dimension set filter. */ public static AssetId findTranslation(ICS ics, AssetId id, String preferredLocaleDimensionIdString, String site) { if (preferredLocaleDimensionIdString == null) { throw new IllegalArgumentException("Required preferred locale dimension ID not provided"); } long preferredDimension = Long.valueOf(preferredLocaleDimensionIdString); long dimensionSetId = locateDimensionSetForSite(ics, site); return findTranslation(ics, id, preferredDimension, dimensionSetId); } /** * Look up the translation for the asset specified, in the locale specified. * <p> * If the desired translation is not available, null will be returned. * <p> * If a dimension set for the site has been configured that returns the * asset other than the preferred locale, that is considered to be fine and * not really the problem of the end user. In other words, a dimension set * may dictate that a "backup" language can be returned to the user. * <p> * Null, however, is a valid option. * * @param ics Content Server context object * @param id id of asset to look up * @param preferredDimension id of locale desired * @param dimensionSetId dimension set to use to find the translation * @return AssetId of translation asset, or null if none is returned by the * dimension set filter. */ public static AssetId findTranslation(ICS ics, AssetId id, long preferredDimension, long dimensionSetId) { if (id == null) { throw new IllegalArgumentException("Required Asset ID missing"); } DimensionableAssetManager mgr = DimensionUtils.getDAM(ics); if (_isInputAssetDimensionPreferred(mgr, id, preferredDimension)) { _log.debug("Input dimension is already in the preferred dimension. Not invoking dimension set filter. Asset: " + id + ", dimension: " + preferredDimension); return id; } else { _log.debug("About to look for translations. Input asset id: " + id + ", dimension set: " + dimensionSetId + ", preferred dimension: " + preferredDimension); } // ***************************************************************************** // The core business logic of this helper class is encapsulated in these // 3 lines DimensionSetInstance dimset = getDimensionSet(ics, dimensionSetId); return findTranslation(ics, id, preferredDimension, dimset); } /** * Look up the translation for the asset specified, in the locale specified. * <p> * If the desired translation is not available, null will be returned. * <p> * If a dimension set has been configured that returns the asset other than * the preferred locale, that is considered to be fine and not really the * problem of the end user. In other words, a dimension set may dictate that * a "backup" language can be returned to the user. * <p> * Null, however, is a valid option. * * @param ics Content Server context object * @param id id of asset to look up * @param preferredDimension id of locale desired * @param dimensionSetName the name of the dimension set to use to find the * translation * @return AssetId of translation asset, or null if none is returned by the * dimension set filter. The id parameters is returned if the asset * does not have a locale or if the locale is already of the * preferredDimension */ public static AssetId findTranslation(ICS ics, AssetId id, long preferredDimension, String dimensionSetName) { if (id == null) { throw new IllegalArgumentException("Required Asset ID missing"); } Dimension locale = DimensionUtils.getLocaleAsDimension(ics, id); if (locale == null) { _log.debug("Asset is not localized. Not invoking dimension set filter. Asset: " + id); return id; } if (locale.getId().getId() == preferredDimension) { _log.debug("Input dimension is already in the preferred dimension. Not invoking dimension set filter. Asset: " + id + ", dimension: " + preferredDimension); return id; } else { _log.debug("About to look for translations. Input asset id: " + id + ", dimension set: " + dimensionSetName + ", preferred dimension: " + preferredDimension); } // ***************************************************************************** // The core business logic of this helper class is encapsulated in these // 3 lines DimensionSetInstance dimset = getDimensionSet(ics, dimensionSetName); return findTranslation(ics, id, preferredDimension, dimset); } /** * @param ics Content Server context object * @param id asset id * @param preferredDimension id for preferred locale * @param dimset dimension set instance * @return assetid of translated asset. * @throws IllegalStateException exception when illegal state is reached */ public static AssetId findTranslation(ICS ics, AssetId id, long preferredDimension, DimensionSetInstance dimset) throws IllegalStateException { AssetId preferredDim = new AssetIdImpl("Dimension", preferredDimension); List<AssetId> preferredDims = Collections.singletonList(preferredDim); Collection<AssetId> relatives = findTranslation(DimensionUtils.getDM(ics), Collections.singletonList(id), preferredDims, dimset); // ***************************************************************************** // make the result pretty if (relatives == null) { _log.warn("No translation found for asset " + id + " in dimension set " + dimset.getId() + " for dimension " + preferredDimension + "."); return null; } else { switch (relatives.size()) { case 0: { _log.warn("No translation found for " + id + " in dimension set " + dimset.getId() + " for dimension " + preferredDimension + "."); // Note May 4, 2010 by Tony Field - this had been changed to // return the input ID but that // is incorrect. The contract clearly states that null is to // be returned if no matching // relatives are found. When null is returned and it is not // expected, often the incorrect // dimension filter is configured. return null; } case 1: { AssetId relative = relatives.iterator().next(); _log.trace("LocaleUtils.findTranslation: RELATIVE FOUND... " + relative.getType() + " '" + relative.getId() + "' // errno = " + ics.GetErrno()); return relative; } default: { throw new IllegalStateException("found more than one translation for asset " + id + " and that is not supposed to be possible."); } } } } /** * Main translation lookup method. Accesses the filter in the dimension set, configures it with the preferred * dimension IDs, then filters the input assets. * * @param dimensionManager manager class for Dimension lookups * @param toFilterList list of input assets that need to be translated. Often it's just one, but a list is perfectly valid. * @param preferredDimensionIds preferred dimensions to be investigated for a result. Priority preference depends on the * configured filter * @param dimSet DimensionSet to use for filtering. * @return list of assets based on the translation rules in the dimension filter from the specified dimension set. */ public static Collection<AssetId> findTranslation(DimensionManager dimensionManager, List<AssetId> toFilterList, Collection<AssetId> preferredDimensionIds, DimensionSetInstance dimSet) { try { return DimensionUtils.filterAssets(dimensionManager, toFilterList, preferredDimensionIds, dimSet); } catch (DimensionException e) { throw new CSRuntimeException("Failed to translate assets. Input assets:" + toFilterList + ", Preferred Dimensions: " + preferredDimensionIds + ", DimensionSet:" + dimSet, ftErrors.exceptionerr, e); } } // /////////////////////////////////////////////////////////////////////////// // Helper functions private static boolean _isInputAssetDimensionPreferred(DimensionableAssetManager mgr, AssetId id, long preferredDimension) { Dimension dim = DimensionUtils.getLocaleAsDimension(mgr, id); if (dim == null) { return true; // if locale not found, tell that the asset is expected } // locale return dim.getId().getId() == preferredDimension; } private static final PreparedStmt FIND_DIMSET_FOR_SITE_PREPAREDSTMT = new PreparedStmt( "select ds.id as id from DimensionSet ds, Publication p, AssetPublication ap where p.name = ? and p.id = ap.pubid and ap.assetid = ds.id and ds.status != 'VO' order by ds.updateddate", Arrays.asList("DimensionSet", "AssetPublication", "Publication")); static { FIND_DIMSET_FOR_SITE_PREPAREDSTMT.setElement(0, "Publication", "name"); } /** * Locates a single dimension set in a site. If no match is found, an * exception is thrown. If more than one match is found, an exception is * thrown. * * @param ics context * @param site site containing a dimension set * @return DimensionSet ID */ public static long locateDimensionSetForSite(ICS ics, String site) { if (site == null) { throw new IllegalArgumentException("Required site name missing"); } StatementParam params = FIND_DIMSET_FOR_SITE_PREPAREDSTMT.newParam(); params.setString(0, site); IList results = ics.SQL(FIND_DIMSET_FOR_SITE_PREPAREDSTMT, params, true); int numRows = results != null && results.hasData() ? results.numRows() : 0; if (numRows == 0) { throw new IllegalStateException( "A DimensionSet has not been defined for site '" + site + "'. Cannot determine any translation unless some locales (Dimensions) are enabled for that site. Aborting operation."); } if (numRows > 1) { String msg = "More than one dimension set found in site " + site + ". Exactly one is expected. Dimension set ids: "; for (IList row : new IterableIListWrapper(results)) { String id = IListUtils.getStringValue(row, "id"); LogDep.logDep(ics, "DimensionSet", id); msg += id + " "; } throw new IllegalStateException(msg + "."); } results.moveTo(1); String id = IListUtils.getStringValue(results, "id"); LogDep.logDep(ics, "DimensionSet", id); if (_log.isTraceEnabled()) { _log.trace("Looked up dimset for site " + site + " and found " + id); } return Long.valueOf(id); } private static DimensionFilterInstance _getPopulatedDimensionFilter(Session ses, DimensionSetInstance dimset, long localeDimensionId) { // Set the filter's preferred dimension // Equivalent to: // %><dimensionset:asset assettype="Dimension" // assetid="<%=localeDimensionId%>" /><% Dimension thePreferredDimension = ((DimensionManager) ses.getManager(DimensionManager.class.getName())) .loadDimension(localeDimensionId); if (thePreferredDimension == null) { throw new RuntimeException("Attempted to load Dimension with id " + localeDimensionId + " but it came back null"); } return _getPopulatedDimensionFilter(dimset, thePreferredDimension); } private static DimensionFilterInstance _getPopulatedDimensionFilter(DimensionSetInstance dimset, Dimension preferredDimension) { DimensionFilterInstance filter; try { filter = dimset.getFilter(); } catch (DimensionException e) { throw new RuntimeException("Could not get Dimension Filter from DimensionSet", e); } filter.setDimensonPreference(Collections.singletonList(preferredDimension)); return filter; } public static DimensionSetInstance getDimensionSet(ICS ics, long theDimSetId) { final String DIMSET_OBJ_NAME = "LocaleUtils:findTranslation:theDimensionSet:DimensionSet"; // Load the site-specific DimensionSet asset ics.SetObj(DIMSET_OBJ_NAME, null); // clear first FTValList args = new FTValList(); args.put("NAME", DIMSET_OBJ_NAME); args.put("TYPE", "DimensionSet"); args.put("OBJECTID", Long.toString(theDimSetId)); args.put("EDITABLE", "FALSE"); ics.runTag("ASSET.LOAD", args); if (ics.GetErrno() < 0) { throw new IllegalStateException("Could not load dimension set. Errno: " + ics.GetErrno()); } Object o = ics.GetObj(DIMSET_OBJ_NAME); if (o == null) { throw new IllegalStateException("Could not load dimension set but we got no errno... unexpected..."); } DimensionSetInstance dimset; if (o instanceof DimensionSetInstance) { dimset = (DimensionSetInstance) o; } else { throw new IllegalStateException("Loaded an asset that is not a Dimension Set"); } return dimset; } public static DimensionSetInstance getDimensionSet(ICS ics, String name) { final String DIMSET_OBJ_NAME = "LocaleUtils:findTranslation:theDimensionSet:DimensionSet"; ics.SetObj(DIMSET_OBJ_NAME, null); AssetLoadByName a = new AssetLoadByName(); a.setAssetType("DimensionSet"); a.setAssetName(name); a.setEditable(false); a.setName(DIMSET_OBJ_NAME); a.execute(ics); if (ics.GetErrno() < 0) { throw new IllegalStateException("Could not load dimension set. Errno: " + ics.GetErrno()); } Object o = ics.GetObj(DIMSET_OBJ_NAME); ics.SetObj(DIMSET_OBJ_NAME, null); if (o == null) { throw new IllegalStateException("Could not load dimension set but we got no errno... unexpected..."); } DimensionSetInstance dimset; if (o instanceof DimensionSetInstance) { dimset = (DimensionSetInstance) o; } else { throw new IllegalStateException("Loaded an asset that is not a Dimension Set"); } return dimset; } }