First cut of multilingual support at the NodeService level

- MLText type is persisted as a Serializable (TODO: investigate alternative storage)
 - 'nodeService' bean is filtered according to the default locale to provide only String return properties
 - Go direct to the 'mlAwareNodeService' bean to get access to unfiltered MLText properties
 - TODO: Proper value searches respecting Locale hierarchy


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@4526 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Derek Hulley
2006-12-06 15:30:30 +00:00
parent 8b65510d6f
commit 5a871bcdd4
13 changed files with 520 additions and 36 deletions

View File

@@ -0,0 +1,191 @@
/*
* Copyright (C) 2006 Alfresco, Inc.
*
* Licensed under the Mozilla Public License version 1.1
* with a permitted attribution clause. You may obtain a
* copy of the License at
*
* http://www.alfresco.org/legal/license.txt
*
* 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.alfresco.service.cmr.repository;
import java.util.Collection;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.alfresco.i18n.I18NUtil;
/**
* Class to represent a multilingual (ML) text value.
* <p>
* The language codes used should conform to the
* {@linkplain http://www.loc.gov/standards/iso639-2/php/English_list.php ISO639-2}
* language code standard, although there is no enforcement of the standard in this
* class.
* <p>
* This is a simple extension of a <code>HashMap</code> with a few convenience methods.
*
* @see <a href=http://www.loc.gov/standards/iso639-2/php/English_list.php>ISO639-2</a>
*
* @author Philippe Dubois
* @author Derek Hulley
*/
public class MLText extends HashMap<Locale, String>
{
private static final long serialVersionUID = -3696135175650511841L;
private Locale defaultLocale;
public MLText()
{
super(3, 0.75F);
}
/**
* Construct an instance with a value corresponding to the current context locale.
*
* @param value the value for the current default locale
*
* @see I18NUtil#getLocale()
* @see #MLText(Locale, String)
* @see #getDefaultValue()
*/
public MLText(String value)
{
this(I18NUtil.getLocale(), value);
}
/**
* Construct an instance with a value for the given locale.
*
* @param locale the locale
* @param value the value
*
* @see #getDefaultValue()
*/
public MLText(Locale locale, String value)
{
super(3, 0.75F);
defaultLocale = locale;
super.put(locale, value);
}
/**
* @return Returns all the language locales defined in the text
*/
public Set<Locale> getLocales()
{
return keySet();
}
/**
* @return Returns all the values stored
*/
public Collection<String> getValues()
{
return values();
}
/**
* Add a multilingual text value
*
* @param locale the language locale
* @param value the multilingual text
*/
public void addValue(Locale locale, String value)
{
put(locale, value);
}
/**
* Retrieve a multilingual text value
*
* @param locale the language locale
*/
public String getValue(Locale locale)
{
return get(locale);
}
/**
* Retrieves a default value from the set of available locales.<br/>
*
* @see I18NUtil#getLocale()
* @see #getClosestValue(Locale)
*/
public String getDefaultValue()
{
Locale locale = I18NUtil.getLocale();
return getClosestValue(locale);
}
/**
* The given locale is used to search for a matching value according to:
* <ul>
* <li>An exact locale match</li>
* <li>A match of locale ISO language codes</li>
* <li>The value for the locale provided in the {@link MLText#MLText(Locale, String) constructor}</li>
* <li>An arbitrary value</li>
* <li><tt>null</tt></li>
* </ul>
*
* @param locale the locale to use as the starting point of the value search
* @return Returns a default <tt>String</tt> value or null if one isn't available.
* <tt>null</tt> will only be returned if there are no values associated with
* this instance. With or without a match, the return value may be <tt>null</tt>.
*/
public String getClosestValue(Locale locale)
{
if (this.size() == 0)
{
return null;
}
// Is there an exact match?
if (containsKey(locale))
{
return get(locale);
}
// Hunt for a similar language
Map.Entry<Locale, String> lastEntry = null;
for (Map.Entry<Locale, String> entry : this.entrySet())
{
lastEntry = entry; // Keep in case we need an arbitrary value later
Locale mapLocale = entry.getKey();
if (mapLocale == null)
{
continue;
}
if (mapLocale.getLanguage().equals(locale.getLanguage()))
{
// we found a language match
return entry.getValue();
}
}
// Nothing found. What about locale as per constructor?
if (containsKey(defaultLocale))
{
return get(defaultLocale);
}
// Still nothing. Just get a value.
return lastEntry.getValue();
}
/**
* Remove a multilingual text value
*
* @param locale the language locale
*/
public void removeValue(Locale locale)
{
remove(locale);
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright (C) 2006 Alfresco, Inc.
*
* Licensed under the Mozilla Public License version 1.1
* with a permitted attribution clause. You may obtain a
* copy of the License at
*
* http://www.alfresco.org/legal/license.txt
*
* 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.alfresco.service.cmr.repository;
import java.util.Locale;
import junit.framework.TestCase;
/**
* @see org.alfresco.service.cmr.repository.MLText
*
* @author Derek Hulley
*/
public class MLTextTest extends TestCase
{
MLText mlText;
@Override
protected void setUp()
{
mlText = new MLText(Locale.CANADA_FRENCH, Locale.CANADA_FRENCH.toString());
mlText.addValue(Locale.US, Locale.US.toString());
mlText.addValue(Locale.UK, Locale.UK.toString());
mlText.addValue(Locale.FRENCH, Locale.FRENCH.toString());
mlText.addValue(Locale.CHINESE, Locale.CHINESE.toString());
}
public void testGetByLocale()
{
// check each value
assertNull("Expected nothing for German", mlText.getValue(Locale.GERMAN));
assertEquals(Locale.US.toString(), mlText.get(Locale.US));
assertEquals(Locale.UK.toString(), mlText.get(Locale.UK));
assertNull("Expected nothing for French general", mlText.getValue(Locale.FRENCH));
// assertEquals("Expected Canada French to be found", Locale.CANADA_FRENCH.toString(),
}
}

View File

@@ -35,6 +35,7 @@ import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.ContentData;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.EntityRef;
import org.alfresco.service.cmr.repository.MLText;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.Path;
import org.alfresco.service.namespace.QName;
@@ -218,31 +219,31 @@ public class DefaultTypeConverter
});
INSTANCE.addConverter(String.class, NodeRef.class, new TypeConverter.Converter<String, NodeRef>()
{
public NodeRef convert(String source)
{
return new NodeRef(source);
}
});
{
public NodeRef convert(String source)
{
return new NodeRef(source);
}
});
INSTANCE.addConverter(String.class, ChildAssociationRef.class, new TypeConverter.Converter<String, ChildAssociationRef>()
{
public ChildAssociationRef convert(String source)
{
return new ChildAssociationRef(source);
}
});
{
public ChildAssociationRef convert(String source)
{
return new ChildAssociationRef(source);
}
});
INSTANCE.addConverter(String.class, AssociationRef.class, new TypeConverter.Converter<String, AssociationRef>()
{
public AssociationRef convert(String source)
{
return new AssociationRef(source);
}
});
{
public AssociationRef convert(String source)
{
return new AssociationRef(source);
}
});
INSTANCE.addConverter(String.class, InputStream.class, new TypeConverter.Converter<String, InputStream>()
{
@@ -259,6 +260,27 @@ public class DefaultTypeConverter
}
});
INSTANCE.addConverter(String.class, MLText.class, new TypeConverter.Converter<String, MLText>()
{
public MLText convert(String source)
{
return new MLText(source);
}
});
//
// From MLText
//
INSTANCE.addConverter(MLText.class, String.class, new TypeConverter.Converter<MLText, String>()
{
public String convert(MLText source)
{
return source.getDefaultValue();
}
});
//
// From enum

View File

@@ -20,9 +20,11 @@ import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Date;
import java.util.Locale;
import junit.framework.TestCase;
import org.alfresco.service.cmr.repository.MLText;
import org.alfresco.util.ISO8601DateFormat;
public class DefaultTypeConverterTest extends TestCase
@@ -88,6 +90,9 @@ public class DefaultTypeConverterTest extends TestCase
assertEquals(ISO8601DateFormat.format(date), DefaultTypeConverter.INSTANCE.convert(String.class, date));
assertEquals("P0Y25D", DefaultTypeConverter.INSTANCE.convert(String.class, new Duration("P0Y25D")));
assertEquals("woof", DefaultTypeConverter.INSTANCE.convert(String.class, "woof"));
MLText mlText = new MLText("woof");
mlText.addValue(Locale.SIMPLIFIED_CHINESE, "");
assertEquals("woof", DefaultTypeConverter.INSTANCE.convert(String.class, mlText));
}
public void testFromString()
@@ -106,6 +111,9 @@ public class DefaultTypeConverterTest extends TestCase
assertEquals("2004-03-12T00:00:00.000Z", ISO8601DateFormat.format(DefaultTypeConverter.INSTANCE.convert(Date.class, "2004-03-12T00:00:00.000Z")));
assertEquals(new Duration("P25D"), DefaultTypeConverter.INSTANCE.convert(Duration.class, "P25D"));
assertEquals("woof", DefaultTypeConverter.INSTANCE.convert(String.class, "woof"));
MLText converted = DefaultTypeConverter.INSTANCE.convert(MLText.class, "woof");
assertEquals("woof", converted.getValue(Locale.getDefault()));
}
public void testPrimativeAccessors()