001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.ui.editors.messagebundle; 029 030import org.opencms.file.CmsFile; 031import org.opencms.file.CmsObject; 032import org.opencms.file.CmsProperty; 033import org.opencms.file.CmsPropertyDefinition; 034import org.opencms.file.CmsResource; 035import org.opencms.file.CmsResource.CmsResourceDeleteMode; 036import org.opencms.file.CmsResourceFilter; 037import org.opencms.file.CmsVfsResourceNotFoundException; 038import org.opencms.i18n.CmsLocaleManager; 039import org.opencms.i18n.CmsMessageContainer; 040import org.opencms.i18n.CmsMessageException; 041import org.opencms.i18n.CmsMessages; 042import org.opencms.loader.CmsLoaderException; 043import org.opencms.lock.CmsLockUtil.LockedFile; 044import org.opencms.main.CmsException; 045import org.opencms.main.CmsIllegalArgumentException; 046import org.opencms.main.CmsLog; 047import org.opencms.main.OpenCms; 048import org.opencms.security.CmsPermissionSet; 049import org.opencms.ui.A_CmsDialogContext; 050import org.opencms.ui.I_CmsDialogContext; 051import org.opencms.ui.I_CmsDialogContext.ContextType; 052import org.opencms.ui.actions.CmsDirectPublishDialogAction; 053import org.opencms.ui.contextmenu.CmsContextMenu; 054import org.opencms.ui.contextmenu.CmsContextMenu.ContextMenuItem; 055import org.opencms.ui.contextmenu.CmsContextMenu.ContextMenuItemClickEvent; 056import org.opencms.ui.contextmenu.CmsContextMenu.ContextMenuItemClickListener; 057import org.opencms.ui.editors.messagebundle.CmsMessageBundleEditorTypes.BundleType; 058import org.opencms.ui.editors.messagebundle.CmsMessageBundleEditorTypes.Descriptor; 059import org.opencms.ui.editors.messagebundle.CmsMessageBundleEditorTypes.EditMode; 060import org.opencms.ui.editors.messagebundle.CmsMessageBundleEditorTypes.EditorState; 061import org.opencms.ui.editors.messagebundle.CmsMessageBundleEditorTypes.EntryChangeEvent; 062import org.opencms.ui.editors.messagebundle.CmsMessageBundleEditorTypes.TableProperty; 063import org.opencms.util.CmsFileUtil; 064import org.opencms.util.CmsStringUtil; 065import org.opencms.util.CmsUUID; 066import org.opencms.xml.CmsXmlException; 067import org.opencms.xml.content.CmsXmlContent; 068import org.opencms.xml.content.CmsXmlContentFactory; 069import org.opencms.xml.content.CmsXmlContentValueSequence; 070import org.opencms.xml.types.I_CmsXmlContentValue; 071 072import java.io.BufferedWriter; 073import java.io.ByteArrayInputStream; 074import java.io.ByteArrayOutputStream; 075import java.io.IOException; 076import java.io.InputStreamReader; 077import java.io.OutputStream; 078import java.io.OutputStreamWriter; 079import java.io.UnsupportedEncodingException; 080import java.io.Writer; 081import java.util.ArrayList; 082import java.util.Collection; 083import java.util.Collections; 084import java.util.Comparator; 085import java.util.HashMap; 086import java.util.HashSet; 087import java.util.List; 088import java.util.Locale; 089import java.util.Map; 090import java.util.Map.Entry; 091import java.util.Properties; 092import java.util.Set; 093 094import org.apache.commons.logging.Log; 095 096import com.vaadin.ui.UI; 097import com.vaadin.v7.data.Item; 098import com.vaadin.v7.data.Property; 099import com.vaadin.v7.data.util.DefaultItemSorter; 100import com.vaadin.v7.data.util.IndexedContainer; 101 102/** 103 * The class contains the logic behind the message translation editor. 104 * In particular it reads / writes the involved files and provides the contents as {@link IndexedContainer}. 105 */ 106public class CmsMessageBundleEditorModel { 107 108 /** Comparator that compares strings case insensitive. */ 109 public static final class CmsCaseInsensitiveStringComparator implements Comparator<Object> { 110 111 /** Single instance of the comparator. */ 112 private static CmsCaseInsensitiveStringComparator m_instance = new CmsCaseInsensitiveStringComparator(); 113 114 /** 115 * Hide the default constructor. 116 */ 117 private CmsCaseInsensitiveStringComparator() { 118 119 // Hide constructor 120 } 121 122 /** 123 * Returns the comparator instance. 124 * @return the comparator 125 */ 126 public static CmsCaseInsensitiveStringComparator getInstance() { 127 128 // is never null, because it's directly initialized on class load. 129 return m_instance; 130 } 131 132 /** 133 * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) 134 */ 135 @SuppressWarnings("unchecked") 136 public int compare(Object o1, Object o2) { 137 138 int r = 0; 139 // Normal non-null comparison 140 if ((o1 != null) && (o2 != null)) { 141 if ((o1 instanceof String) && (o2 instanceof String)) { 142 String string1 = (String)o1; 143 String string2 = (String)o2; 144 r = String.CASE_INSENSITIVE_ORDER.compare(string1, string2); 145 if (r == 0) { 146 r = string1.compareTo(string2); 147 } 148 } else { 149 // Assume the objects can be cast to Comparable, throw 150 // ClassCastException otherwise. 151 r = ((Comparable<Object>)o1).compareTo(o2); 152 } 153 } else if (o1 == o2) { 154 // Objects are equal if both are null 155 r = 0; 156 } else { 157 if (o1 == null) { 158 r = -1; // null is less than non-null 159 } else { 160 r = 1; // non-null is greater than null 161 } 162 } 163 164 return r; 165 } 166 } 167 168 /** Wrapper for the configurable messages for the column headers of the message bundle editor. */ 169 public static final class ConfigurableMessages { 170 171 /** The messages from the default message bundle. */ 172 CmsMessages m_defaultMessages; 173 /** The messages from a configured message bundle, overwriting the ones from the default bundle. */ 174 CmsMessages m_configuredMessages; 175 176 /** 177 * Default constructor. 178 * @param defaultMessages the default messages. 179 * @param locale the locale in which the messages are requested. 180 * @param configuredBundle the base name of the configured message bundle (can be <code>null</code>). 181 */ 182 public ConfigurableMessages(CmsMessages defaultMessages, Locale locale, String configuredBundle) { 183 184 m_defaultMessages = defaultMessages; 185 if (null != configuredBundle) { 186 CmsMessages bundle = new CmsMessages(configuredBundle, locale); 187 if (null != bundle.getResourceBundle()) { 188 m_configuredMessages = bundle; 189 } 190 } 191 } 192 193 /** 194 * Returns the localized column header. 195 * @param column the column's property (name). 196 * @return the localized columen header. 197 */ 198 public String getColumnHeader(TableProperty column) { 199 200 switch (column) { 201 case DEFAULT: 202 return getMessage(Messages.GUI_COLUMN_HEADER_DEFAULT_0); 203 case DESCRIPTION: 204 return getMessage(Messages.GUI_COLUMN_HEADER_DESCRIPTION_0); 205 case KEY: 206 return getMessage(Messages.GUI_COLUMN_HEADER_KEY_0); 207 case OPTIONS: 208 return ""; 209 case TRANSLATION: 210 return getMessage(Messages.GUI_COLUMN_HEADER_TRANSLATION_0); 211 default: 212 throw new IllegalArgumentException(); 213 } 214 } 215 216 /** 217 * Returns the message for the key, either from the configured bundle, or - if not found - from the default bundle. 218 * 219 * @param key message key. 220 * @return the message for the key. 221 */ 222 private String getMessage(String key) { 223 224 if (null != m_configuredMessages) { 225 try { 226 return m_configuredMessages.getString(key); 227 } catch (CmsMessageException e) { 228 // do nothing - use default messages 229 } 230 } 231 try { 232 return m_defaultMessages.getString(key); 233 } catch (CmsMessageException e) { 234 return "???" + key + "???"; 235 } 236 } 237 238 } 239 240 /** Extension of {@link Properties} to allow saving with keys alphabetically ordered and without time stamp as first comment. 241 * 242 * NOTE: Can't handle comments. They are just discarded. 243 * NOTE: Most of the class is just a plain copy of the private methods of {@link Properties}, so be aware that adjustments may be necessary if the {@link Properties} implementation changes. 244 * NOTE: The solution was taken to guarantee correct escaping when storing properties. 245 * 246 */ 247 public static final class SortedProperties extends Properties { 248 249 /** Serialization id to implement Serializable. */ 250 private static final long serialVersionUID = 8814525892788043348L; 251 252 /** 253 * NOTE: This is a one-to-one copy from {@link java.util.Properties} with HEX_DIGIT renamed to HEX_DIGIT. 254 * A table of hex digits. 255 */ 256 private static final char[] HEX_DIGIT = { 257 '0', 258 '1', 259 '2', 260 '3', 261 '4', 262 '5', 263 '6', 264 '7', 265 '8', 266 '9', 267 'A', 268 'B', 269 'C', 270 'D', 271 'E', 272 'F'}; 273 274 /** 275 * Default constructor. 276 */ 277 public SortedProperties() { 278 279 super(); 280 } 281 282 /** 283 * NOTE: This is a one-to-one copy from {@link java.util.Properties} 284 * Convert a nibble to a hex character. 285 * @param nibble the nibble to convert. 286 * @return the character as Hex 287 */ 288 private static char toHex(int nibble) { 289 290 return HEX_DIGIT[(nibble & 0xF)]; 291 } 292 293 /** 294 * Override to omit the date comment. 295 * @see java.util.Properties#store(java.io.OutputStream, java.lang.String) 296 */ 297 @Override 298 public void store(OutputStream out, String comments) throws IOException { 299 300 store0(new BufferedWriter(new OutputStreamWriter(out, "8859_1")), true); 301 } 302 303 /** 304 * Override to omit the date comment. 305 * @see java.util.Properties#store(java.io.Writer, java.lang.String) 306 */ 307 @Override 308 public void store(Writer writer, String comments) throws IOException { 309 310 store0((writer instanceof BufferedWriter) ? (BufferedWriter)writer : new BufferedWriter(writer), false); 311 } 312 313 /** 314 * NOTE: This is a one-to-one copy of the private method from {@link java.util.Properties} 315 * Converts unicodes to encoded \uxxxx and escapes 316 * special characters with a preceding slash. 317 * 318 * @param theString string to convert 319 * @param escapeSpace flag, indicating if spaces should be escaped 320 * @param escapeUnicode flag, indicating if unicode signs should be escaped 321 * @return the converted string 322 */ 323 private String saveConvert(String theString, boolean escapeSpace, boolean escapeUnicode) { 324 325 int len = theString.length(); 326 int bufLen = len * 2; 327 if (bufLen < 0) { 328 bufLen = Integer.MAX_VALUE; 329 } 330 StringBuffer outBuffer = new StringBuffer(bufLen); 331 332 for (int x = 0; x < len; x++) { 333 char aChar = theString.charAt(x); 334 // Handle common case first, selecting largest block that 335 // avoids the specials below 336 if ((aChar > 61) && (aChar < 127)) { 337 if (aChar == '\\') { 338 outBuffer.append('\\'); 339 outBuffer.append('\\'); 340 continue; 341 } 342 outBuffer.append(aChar); 343 continue; 344 } 345 switch (aChar) { 346 case ' ': 347 if ((x == 0) || escapeSpace) { 348 outBuffer.append('\\'); 349 } 350 outBuffer.append(' '); 351 break; 352 case '\t': 353 outBuffer.append('\\'); 354 outBuffer.append('t'); 355 break; 356 case '\n': 357 outBuffer.append('\\'); 358 outBuffer.append('n'); 359 break; 360 case '\r': 361 outBuffer.append('\\'); 362 outBuffer.append('r'); 363 break; 364 case '\f': 365 outBuffer.append('\\'); 366 outBuffer.append('f'); 367 break; 368 case '=': // Fall through 369 case ':': // Fall through 370 case '#': // Fall through 371 case '!': 372 outBuffer.append('\\'); 373 outBuffer.append(aChar); 374 break; 375 default: 376 if (((aChar < 0x0020) || (aChar > 0x007e)) & escapeUnicode) { 377 outBuffer.append('\\'); 378 outBuffer.append('u'); 379 outBuffer.append(toHex((aChar >> 12) & 0xF)); 380 outBuffer.append(toHex((aChar >> 8) & 0xF)); 381 outBuffer.append(toHex((aChar >> 4) & 0xF)); 382 outBuffer.append(toHex(aChar & 0xF)); 383 } else { 384 outBuffer.append(aChar); 385 } 386 } 387 } 388 return outBuffer.toString(); 389 } 390 391 /** 392 * Adaption of {@link java.util.Properties#store0(BufferedWriter,String,boolean)}. 393 * The behavior differs as follows:<ul> 394 * <li>Comments are not handled.</li> 395 * <li>The time stamp comment is omited.</li> 396 * <li>Messages are stored sorted alphabetically by key</li> 397 * </ul> 398 * @param bw writer to write to 399 * @param escUnicode flag, indicating if unicode characters should be escaped 400 * @throws IOException 401 */ 402 @SuppressWarnings("javadoc") 403 private void store0(BufferedWriter bw, boolean escUnicode) throws IOException { 404 405 synchronized (this) { 406 List<Object> keys = new ArrayList<Object>(super.keySet()); 407 Collections.sort(keys, CmsCaseInsensitiveStringComparator.getInstance()); 408 for (Object k : keys) { 409 String key = (String)k; 410 String val = (String)get(key); 411 key = saveConvert(key, true, escUnicode); 412 /* No need to escape embedded and trailing spaces for value, hence 413 * pass false to flag. 414 */ 415 val = saveConvert(val, false, escUnicode); 416 bw.write(key + "=" + val); 417 bw.newLine(); 418 } 419 } 420 bw.flush(); 421 } 422 } 423 424 /** The result of a key change. */ 425 enum KeyChangeResult { 426 /** Key change was successful. */ 427 SUCCESS, 428 /** Key change failed, because the new key already exists. */ 429 FAILED_DUPLICATED_KEY, 430 /** Key change failed, because the key could not be changed for one or more languages. */ 431 FAILED_FOR_OTHER_LANGUAGE 432 } 433 434 /** The log object for this class. */ 435 private static final Log LOG = CmsLog.getLog(CmsMessageBundleEditorModel.class); 436 437 /** The property for configuring the message bundle used for localizing the bundle descriptors entries. */ 438 public static final String PROPERTY_BUNDLE_DESCRIPTOR_LOCALIZATION = "bundle.descriptor.messages"; 439 440 /** CmsObject for read / write operations. */ 441 private CmsObject m_cms; 442 /** The files currently edited. */ 443 private Map<Locale, LockedFile> m_lockedBundleFiles; 444 /** The files of the bundle. */ 445 private Map<Locale, CmsResource> m_bundleFiles; 446 /** The resource that was opened with the editor. */ 447 private CmsResource m_resource; 448 /** The bundle descriptor resource. */ 449 private CmsResource m_desc; 450 /** The bundle descriptor as unmarshalled XML Content. */ 451 private CmsXmlContent m_descContent; 452 /** The xml bundle edited (or null, if a property bundle is edited). */ 453 private CmsXmlContent m_xmlBundle; 454 /** The already loaded localizations. */ 455 private Map<Locale, SortedProperties> m_localizations; 456 /** The bundle's base name. */ 457 private String m_basename; 458 /** The site path to the folder where the edited resource is in. */ 459 private String m_sitepath; 460 /** The currently edited locale. */ 461 private Locale m_locale; 462 /** The type of the loaded bundle. */ 463 private CmsMessageBundleEditorTypes.BundleType m_bundleType; 464 465 /** The complete key set as map from keys to the number of occurrences. */ 466 CmsMessageBundleEditorTypes.KeySet m_keyset; 467 468 /** Containers holding the keys for each locale. */ 469 private IndexedContainer m_container; 470 /** The available locales. */ 471 private Collection<Locale> m_locales; 472 /** Map from edit mode to the editor state. */ 473 private Map<CmsMessageBundleEditorTypes.EditMode, EditorState> m_editorState; 474 /** Flag, indicating if a master edit mode is available. */ 475 private boolean m_hasMasterMode; 476 /** The current edit mode. */ 477 private CmsMessageBundleEditorTypes.EditMode m_editMode; 478 479 /** Descriptor file, if edited besides a bundle. */ 480 private LockedFile m_descFile; 481 482 /** The configured resource bundle used for the column headings of the bundle descriptor. */ 483 private String m_configuredBundle; 484 485 /** Flag, indicating if the locale of the bundle that is edited has switched on opening. */ 486 private boolean m_switchedLocaleOnOpening; 487 488 /** Flag, indicating if at least one default value is present in the current descriptor. */ 489 private boolean m_hasDefault; 490 491 /** Flag, indicating if at least one description is present in the current descriptor. */ 492 private boolean m_hasDescription; 493 494 /** Flag, indicating if the descriptor should be removed when editing is cancelled. */ 495 private boolean m_removeDescriptorOnCancel; 496 497 /** Flag, indicating if something changed. */ 498 Set<Locale> m_changedTranslations; 499 500 /** Flag, indicating if something changed. */ 501 boolean m_descriptorHasChanges; 502 503 /** Flag, indicating if all localizations have already been loaded. */ 504 private boolean m_alreadyLoadedAllLocalizations; 505 506 /** 507 * 508 * @param cms the {@link CmsObject} used for reading / writing. 509 * @param resource the file that is opened for editing. 510 * @throws CmsException thrown if reading some of the involved {@link CmsResource}s is not possible. 511 * @throws IOException initialization of a property bundle fails 512 */ 513 public CmsMessageBundleEditorModel(CmsObject cms, CmsResource resource) 514 throws CmsException, IOException { 515 516 if (cms == null) { 517 throw new CmsException(Messages.get().container(Messages.ERR_LOADING_BUNDLE_CMS_OBJECT_NULL_0)); 518 } 519 520 if (resource == null) { 521 throw new CmsException(Messages.get().container(Messages.ERR_LOADING_BUNDLE_FILENAME_NULL_0)); 522 } 523 524 m_cms = cms; 525 m_resource = resource; 526 m_editMode = CmsMessageBundleEditorTypes.EditMode.DEFAULT; 527 528 m_bundleFiles = new HashMap<Locale, CmsResource>(); 529 m_lockedBundleFiles = new HashMap<Locale, LockedFile>(); 530 m_changedTranslations = new HashSet<Locale>(); 531 m_localizations = new HashMap<Locale, SortedProperties>(); 532 m_keyset = new CmsMessageBundleEditorTypes.KeySet(); 533 534 m_bundleType = initBundleType(); 535 536 m_locales = initLocales(); 537 538 //IMPORTANT: The order of the following method calls is important. 539 540 if (m_bundleType.equals(CmsMessageBundleEditorTypes.BundleType.XML)) { 541 initXmlBundle(); 542 } 543 544 setResourceInformation(); 545 546 initDescriptor(); 547 548 if (m_bundleType.equals(CmsMessageBundleEditorTypes.BundleType.PROPERTY)) { 549 initPropertyBundle(); 550 } 551 552 initHasMasterMode(); 553 554 initEditorStates(); 555 556 } 557 558 /** 559 * Creates a descriptor for the currently edited message bundle. 560 * @return <code>true</code> if the descriptor could be created, <code>false</code> otherwise. 561 */ 562 public boolean addDescriptor() { 563 564 saveLocalization(); 565 IndexedContainer oldContainer = m_container; 566 try { 567 createAndLockDescriptorFile(); 568 unmarshalDescriptor(); 569 updateBundleDescriptorContent(); 570 m_hasMasterMode = true; 571 m_container = createContainer(); 572 m_editorState.put(EditMode.DEFAULT, getDefaultState()); 573 m_editorState.put(EditMode.MASTER, getMasterState()); 574 } catch (CmsException | IOException e) { 575 LOG.error(e.getLocalizedMessage(), e); 576 if (m_descContent != null) { 577 m_descContent = null; 578 } 579 if (m_descFile != null) { 580 m_descFile = null; 581 } 582 if (m_desc != null) { 583 try { 584 m_cms.deleteResource(m_desc, CmsResourceDeleteMode.valueOf(1)); 585 } catch (CmsException ex) { 586 LOG.error(ex.getLocalizedMessage(), ex); 587 } 588 m_desc = null; 589 } 590 m_hasMasterMode = false; 591 m_container = oldContainer; 592 return false; 593 } 594 m_removeDescriptorOnCancel = true; 595 return true; 596 } 597 598 /** 599 * Returns a flag, indicating if keys can be added in the current edit mode. 600 * @return a flag, indicating if keys can be added in the current edit mode. 601 */ 602 public boolean canAddKeys() { 603 604 return !hasDescriptor() || getEditMode().equals(EditMode.MASTER); 605 } 606 607 /** 608 * When the descriptor was added while editing, but the change was not saved, it has to be removed 609 * when the editor is closed. 610 * @throws CmsException thrown when deleting the descriptor resource fails 611 */ 612 public void deleteDescriptorIfNecessary() throws CmsException { 613 614 if (m_removeDescriptorOnCancel && (m_desc != null)) { 615 m_cms.deleteResource(m_desc, CmsResourceDeleteMode.valueOf(2)); 616 } 617 618 } 619 620 /** Returns the a set with all keys that are used at least in one translation. 621 * @return the a set with all keys that are used at least in one translation. 622 */ 623 public Set<Object> getAllUsedKeys() { 624 625 return m_keyset.getKeySet(); 626 } 627 628 /** Returns the type of the currently edited bundle. 629 * @return the type of the currently edited bundle. 630 */ 631 public BundleType getBundleType() { 632 633 return m_bundleType; 634 } 635 636 /** 637 * Returns the configured bundle, or the provided default bundle. 638 * @param defaultMessages the default bundle 639 * @param locale the preferred locale 640 * @return the configured bundle or, if not found, the default bundle. 641 */ 642 public ConfigurableMessages getConfigurableMessages(CmsMessages defaultMessages, Locale locale) { 643 644 return new ConfigurableMessages(defaultMessages, locale, m_configuredBundle); 645 646 } 647 648 /** 649 * Returns the container filled according to the current locale. 650 * @return the container filled according to the current locale. 651 * @throws IOException thrown if reading a bundle resource fails. 652 * @throws CmsException thrown if reading a bundle resource fails. 653 */ 654 public IndexedContainer getContainerForCurrentLocale() throws IOException, CmsException { 655 656 if (null == m_container) { 657 m_container = createContainer(); 658 } 659 return m_container; 660 } 661 662 /** 663 * Returns the context menu for the table item. 664 * @param itemId the table item. 665 * @return the context menu for the given item. 666 */ 667 public CmsContextMenu getContextMenuForItem(Object itemId) { 668 669 CmsContextMenu result = null; 670 try { 671 final Item item = m_container.getItem(itemId); 672 Property<?> keyProp = item.getItemProperty(TableProperty.KEY); 673 String key = (String)keyProp.getValue(); 674 if ((null != key) && !key.isEmpty()) { 675 loadAllRemainingLocalizations(); 676 final Map<Locale, String> localesWithEntries = new HashMap<Locale, String>(); 677 for (Locale l : m_localizations.keySet()) { 678 if (l != m_locale) { 679 String value = m_localizations.get(l).getProperty(key); 680 if ((null != value) && !value.isEmpty()) { 681 localesWithEntries.put(l, value); 682 } 683 } 684 } 685 if (!localesWithEntries.isEmpty()) { 686 result = new CmsContextMenu(); 687 ContextMenuItem mainItem = result.addItem( 688 Messages.get().getBundle(UI.getCurrent().getLocale()).key( 689 Messages.GUI_BUNDLE_EDITOR_CONTEXT_COPY_LOCALE_0)); 690 for (final Locale l : localesWithEntries.keySet()) { 691 692 ContextMenuItem menuItem = mainItem.addItem(l.getDisplayName(UI.getCurrent().getLocale())); 693 menuItem.addItemClickListener(new ContextMenuItemClickListener() { 694 695 public void contextMenuItemClicked(ContextMenuItemClickEvent event) { 696 697 item.getItemProperty(TableProperty.TRANSLATION).setValue(localesWithEntries.get(l)); 698 699 } 700 }); 701 } 702 } 703 } 704 } catch (Exception e) { 705 LOG.error(e.getLocalizedMessage(), e); 706 //TODO: Improve 707 } 708 return result; 709 } 710 711 /** 712 * Returns the editable columns for the current edit mode. 713 * @return the editable columns for the current edit mode. 714 */ 715 public List<TableProperty> getEditableColumns() { 716 717 return m_editorState.get(m_editMode).getEditableColumns(); 718 } 719 720 /** 721 * Returns the editable columns for the provided edit mode. 722 * @param mode the edit mode. 723 * @return the editable columns for the provided edit mode. 724 */ 725 public List<TableProperty> getEditableColumns(CmsMessageBundleEditorTypes.EditMode mode) { 726 727 return m_editorState.get(mode).getEditableColumns(); 728 } 729 730 /** 731 * Returns the site path for the edited bundle file. 732 * 733 * @return the site path for the edited bundle file. 734 */ 735 public String getEditedFilePath() { 736 737 switch (getBundleType()) { 738 case DESCRIPTOR: 739 return m_cms.getSitePath(m_desc); 740 case PROPERTY: 741 return null != m_lockedBundleFiles.get(getLocale()) 742 ? m_cms.getSitePath(m_lockedBundleFiles.get(getLocale()).getFile()) 743 : m_cms.getSitePath(m_resource); 744 case XML: 745 return m_cms.getSitePath(m_resource); 746 default: 747 throw new IllegalArgumentException(); 748 } 749 } 750 751 /** Returns the current edit mode. 752 * @return the current edit mode. 753 */ 754 public CmsMessageBundleEditorTypes.EditMode getEditMode() { 755 756 return m_editMode; 757 } 758 759 /** 760 * Returns the currently edited locale. 761 * 762 * @return the currently edited locale. 763 */ 764 public Locale getLocale() { 765 766 return m_locale; 767 } 768 769 /** 770 * Returns the locales available for the specific resource. 771 * 772 * @return the locales available for the specific resource. 773 */ 774 public Collection<Locale> getLocales() { 775 776 return m_locales; 777 } 778 779 /** 780 * Returns a flag, indicating if the locale has been switched on opening. 781 * @return a flag, indicating if the locale has been switched on opening. 782 */ 783 public boolean getSwitchedLocaleOnOpening() { 784 785 return m_switchedLocaleOnOpening; 786 } 787 788 /** 789 * Handles the change of a value in the current translation. 790 * @param propertyId the property id of the column where the value has changed. 791 */ 792 public void handleChange(Object propertyId) { 793 794 try { 795 lockOnChange(propertyId); 796 } catch (CmsException e) { 797 LOG.debug(e.getLocalizedMessage(), e); 798 } 799 if (isDescriptorProperty(propertyId)) { 800 m_descriptorHasChanges = true; 801 } 802 if (isBundleProperty(propertyId)) { 803 m_changedTranslations.add(getLocale()); 804 } 805 806 } 807 808 /** 809 * Handles a key change. 810 * 811 * @param event the key change event. 812 * @param allLanguages <code>true</code> for changing the key for all languages, <code>false</code> if the key should be changed only for the current language. 813 * @return result, indicating if the key change was successful. 814 */ 815 public KeyChangeResult handleKeyChange(EntryChangeEvent event, boolean allLanguages) { 816 817 if (m_keyset.getKeySet().contains(event.getNewValue())) { 818 m_container.getItem(event.getItemId()).getItemProperty(TableProperty.KEY).setValue(event.getOldValue()); 819 return KeyChangeResult.FAILED_DUPLICATED_KEY; 820 } 821 if (allLanguages && !renameKeyForAllLanguages(event.getOldValue(), event.getNewValue())) { 822 m_container.getItem(event.getItemId()).getItemProperty(TableProperty.KEY).setValue(event.getOldValue()); 823 return KeyChangeResult.FAILED_FOR_OTHER_LANGUAGE; 824 } 825 return KeyChangeResult.SUCCESS; 826 } 827 828 /** 829 * Handles the deletion of a key. 830 * @param key the deleted key. 831 * @return <code>true</code> if the deletion was successful, <code>false</code> otherwise. 832 */ 833 public boolean handleKeyDeletion(final String key) { 834 835 if (m_keyset.getKeySet().contains(key)) { 836 if (removeKeyForAllLanguages(key)) { 837 m_keyset.removeKey(key); 838 return true; 839 } else { 840 return false; 841 } 842 } 843 return true; 844 } 845 846 /** 847 * Returns a flag, indicating if something was edited. 848 * @return a flag, indicating if something was edited. 849 */ 850 public boolean hasChanges() { 851 852 return !m_changedTranslations.isEmpty() || m_descriptorHasChanges; 853 } 854 855 /** 856 * Returns a flag, indicating if the descriptor specifies any default values. 857 * @return flag, indicating if the descriptor specifies any default values. 858 */ 859 public boolean hasDefaultValues() { 860 861 return m_hasDefault; 862 } 863 864 /** 865 * Returns a flag, indicating if the descriptor specifies any descriptions. 866 * @return a flag, indicating if the descriptor specifies any descriptions. 867 */ 868 public boolean hasDescriptionValues() { 869 870 return m_hasDescription; 871 } 872 873 /** Returns a flag, indicating if a bundle descriptor is present. 874 * @return flag, indicating if a bundle descriptor is present. 875 */ 876 public boolean hasDescriptor() { 877 878 return !m_bundleType.equals(CmsMessageBundleEditorTypes.BundleType.DESCRIPTOR) && (m_descContent != null); 879 } 880 881 /** 882 * Returns a flag, indicating if a master edit mode is available. 883 * @return a flag, indicating if a master edit mode is available. 884 */ 885 public boolean hasMasterMode() { 886 887 return m_hasMasterMode; 888 } 889 890 /** 891 * Returns a flag, indicating if the options column (with add and delete option for rows) 892 * should be shown in the given edit mode. 893 * @param mode the edit mode for which the column option is requested. 894 * @return a flag, indicating if the options column (with add and delete option for rows) 895 */ 896 public boolean isShowOptionsColumn(CmsMessageBundleEditorTypes.EditMode mode) { 897 898 return m_editorState.get(mode).isShowOptions(); 899 } 900 901 /** 902 * Publish the bundle resources directly. 903 */ 904 public void publish() { 905 906 CmsDirectPublishDialogAction action = new CmsDirectPublishDialogAction(); 907 List<CmsResource> resources = getBundleResources(); 908 I_CmsDialogContext context = new A_CmsDialogContext("", ContextType.appToolbar, resources) { 909 910 public void focus(CmsUUID structureId) { 911 912 //Nothing to do. 913 } 914 915 public List<CmsUUID> getAllStructureIdsInView() { 916 917 return null; 918 } 919 920 public void updateUserInfo() { 921 922 //Nothing to do. 923 } 924 }; 925 action.executeAction(context); 926 updateLockInformation(); 927 928 } 929 930 /** 931 * Saves the messages for all languages that were opened in the editor. 932 * 933 * @throws CmsException thrown if saving fails. 934 */ 935 public void save() throws CmsException { 936 937 if (hasChanges()) { 938 switch (m_bundleType) { 939 case PROPERTY: 940 saveLocalization(); 941 saveToPropertyVfsBundle(); 942 break; 943 944 case XML: 945 saveLocalization(); 946 saveToXmlVfsBundle(); 947 948 break; 949 950 case DESCRIPTOR: 951 break; 952 default: 953 throw new IllegalArgumentException(); 954 } 955 if (null != m_descFile) { 956 saveToBundleDescriptor(); 957 } 958 959 resetChanges(); 960 } 961 962 } 963 964 /** 965 * Saves the loaded XML bundle as property bundle. 966 * @throws UnsupportedEncodingException thrown if localizations from the XML bundle could not be loaded correctly. 967 * @throws CmsException thrown if any of the interactions with the VFS fails. 968 * @throws IOException thrown if localizations from the XML bundle could not be loaded correctly. 969 */ 970 public void saveAsPropertyBundle() throws UnsupportedEncodingException, CmsException, IOException { 971 972 switch (m_bundleType) { 973 case XML: 974 saveLocalization(); 975 loadAllRemainingLocalizations(); 976 createPropertyVfsBundleFiles(); 977 saveToPropertyVfsBundle(); 978 m_bundleType = BundleType.PROPERTY; 979 removeXmlBundleFile(); 980 981 break; 982 default: 983 throw new IllegalArgumentException( 984 "The method should only be called when editing an XMLResourceBundle."); 985 } 986 987 } 988 989 /** 990 * Set the edit mode. 991 * @param mode the edit mode to set. 992 * @return flag, indicating if the mode could be changed. 993 */ 994 public boolean setEditMode(CmsMessageBundleEditorTypes.EditMode mode) { 995 996 try { 997 if ((mode == CmsMessageBundleEditorTypes.EditMode.MASTER) && (null == m_descFile)) { 998 m_descFile = LockedFile.lockResource(m_cms, m_desc); 999 } 1000 m_editMode = mode; 1001 } catch (CmsException e) { 1002 return false; 1003 } 1004 return true; 1005 } 1006 1007 /** 1008 * Set the currently edited locale. 1009 * 1010 * @param locale the currently edited locale. 1011 * @return <code>true</code> if the locale could be set, <code>false</code> otherwise. 1012 */ 1013 public boolean setLocale(Locale locale) { 1014 1015 if (adjustExistingContainer(locale)) { 1016 m_locale = locale; 1017 return true; 1018 } 1019 return false; 1020 } 1021 1022 /** 1023 * Unlock all files opened for writing. 1024 */ 1025 public void unlock() { 1026 1027 for (Locale l : m_lockedBundleFiles.keySet()) { 1028 LockedFile f = m_lockedBundleFiles.get(l); 1029 f.tryUnlock(); 1030 } 1031 if (null != m_descFile) { 1032 m_descFile.tryUnlock(); 1033 } 1034 } 1035 1036 /** 1037 * Returns all resources that belong to the bundle 1038 * This includes the descriptor if one exists. 1039 * 1040 * @return List of the bundle resources, including the descriptor. 1041 */ 1042 List<CmsResource> getBundleResources() { 1043 1044 List<CmsResource> resources = new ArrayList<>(m_bundleFiles.values()); 1045 if (m_desc != null) { 1046 resources.add(m_desc); 1047 } 1048 return resources; 1049 1050 } 1051 1052 /** 1053 * Adjusts the locale for an already existing container by first saving the translation for the current locale and the loading the values of the new locale. 1054 * 1055 * @param locale the locale for which the container should be adjusted. 1056 * @return <code>true</code> if the locale could be switched, <code>false</code> otherwise. 1057 */ 1058 private boolean adjustExistingContainer(Locale locale) { 1059 1060 saveLocalization(); 1061 return replaceValues(locale); 1062 1063 } 1064 1065 /** 1066 * Creates a descriptor for the bundle in the same folder where the bundle files are located. 1067 * @throws CmsException thrown if creation fails. 1068 */ 1069 private void createAndLockDescriptorFile() throws CmsException { 1070 1071 String sitePath = m_sitepath + m_basename + CmsMessageBundleEditorTypes.Descriptor.POSTFIX; 1072 m_desc = m_cms.createResource( 1073 sitePath, 1074 OpenCms.getResourceManager().getResourceType(CmsMessageBundleEditorTypes.BundleType.DESCRIPTOR.toString())); 1075 m_descFile = LockedFile.lockResource(m_cms, m_desc); 1076 m_descFile.setCreated(true); 1077 } 1078 1079 /** 1080 * Initializes the IndexedContainer shown in the table for the current locale. 1081 * Therefore, the involved {@link CmsResource}s will be read, if not already done. 1082 * @return the created container 1083 * @throws IOException thrown if reading of an involved file fails. 1084 * @throws CmsException thrown if reading of an involved file fails. 1085 */ 1086 private IndexedContainer createContainer() throws IOException, CmsException { 1087 1088 IndexedContainer container = null; 1089 1090 if (m_bundleType.equals(CmsMessageBundleEditorTypes.BundleType.DESCRIPTOR)) { 1091 container = createContainerForDescriptorEditing(); 1092 } else { 1093 if (hasDescriptor()) { 1094 container = createContainerForBundleWithDescriptor(); 1095 } else { 1096 container = createContainerForBundleWithoutDescriptor(); 1097 } 1098 } 1099 container.setItemSorter(new DefaultItemSorter(CmsCaseInsensitiveStringComparator.getInstance())); 1100 return container; 1101 } 1102 1103 /** 1104 * Creates the container for a bundle with descriptor. 1105 * @return the container for a bundle with descriptor. 1106 * @throws IOException thrown if reading the bundle fails. 1107 * @throws CmsException thrown if reading the bundle fails. 1108 */ 1109 private IndexedContainer createContainerForBundleWithDescriptor() throws IOException, CmsException { 1110 1111 IndexedContainer container = new IndexedContainer(); 1112 1113 // create properties 1114 container.addContainerProperty(TableProperty.KEY, String.class, ""); 1115 container.addContainerProperty(TableProperty.DESCRIPTION, String.class, ""); 1116 container.addContainerProperty(TableProperty.DEFAULT, String.class, ""); 1117 container.addContainerProperty(TableProperty.TRANSLATION, String.class, ""); 1118 1119 // add entries 1120 SortedProperties localization = getLocalization(m_locale); 1121 CmsXmlContentValueSequence messages = m_descContent.getValueSequence(Descriptor.N_MESSAGE, Descriptor.LOCALE); 1122 String descValue; 1123 boolean hasDescription = false; 1124 String defaultValue; 1125 boolean hasDefault = false; 1126 for (int i = 0; i < messages.getElementCount(); i++) { 1127 1128 String prefix = messages.getValue(i).getPath() + "/"; 1129 Object itemId = container.addItem(); 1130 Item item = container.getItem(itemId); 1131 String key = m_descContent.getValue(prefix + Descriptor.N_KEY, Descriptor.LOCALE).getStringValue(m_cms); 1132 item.getItemProperty(TableProperty.KEY).setValue(key); 1133 String translation = localization.getProperty(key); 1134 item.getItemProperty(TableProperty.TRANSLATION).setValue(null == translation ? "" : translation); 1135 descValue = m_descContent.getValue(prefix + Descriptor.N_DESCRIPTION, Descriptor.LOCALE).getStringValue( 1136 m_cms); 1137 item.getItemProperty(TableProperty.DESCRIPTION).setValue(descValue); 1138 hasDescription = hasDescription || !descValue.isEmpty(); 1139 defaultValue = m_descContent.getValue(prefix + Descriptor.N_DEFAULT, Descriptor.LOCALE).getStringValue( 1140 m_cms); 1141 item.getItemProperty(TableProperty.DEFAULT).setValue(defaultValue); 1142 hasDefault = hasDefault || !defaultValue.isEmpty(); 1143 } 1144 1145 m_hasDefault = hasDefault; 1146 m_hasDescription = hasDescription; 1147 return container; 1148 1149 } 1150 1151 /** 1152 * Creates the container for a bundle without descriptor. 1153 * @return the container for a bundle without descriptor. 1154 * @throws IOException thrown if reading the bundle fails. 1155 * @throws CmsException thrown if reading the bundle fails. 1156 */ 1157 private IndexedContainer createContainerForBundleWithoutDescriptor() throws IOException, CmsException { 1158 1159 IndexedContainer container = new IndexedContainer(); 1160 1161 // create properties 1162 container.addContainerProperty(TableProperty.KEY, String.class, ""); 1163 container.addContainerProperty(TableProperty.TRANSLATION, String.class, ""); 1164 1165 // add entries 1166 SortedProperties localization = getLocalization(m_locale); 1167 Set<Object> keySet = m_keyset.getKeySet(); 1168 for (Object key : keySet) { 1169 1170 Object itemId = container.addItem(); 1171 Item item = container.getItem(itemId); 1172 item.getItemProperty(TableProperty.KEY).setValue(key); 1173 Object translation = localization.get(key); 1174 item.getItemProperty(TableProperty.TRANSLATION).setValue(null == translation ? "" : translation); 1175 } 1176 1177 return container; 1178 } 1179 1180 /** 1181 * Creates the container for a bundle descriptor. 1182 * @return the container for a bundle descriptor. 1183 */ 1184 private IndexedContainer createContainerForDescriptorEditing() { 1185 1186 IndexedContainer container = new IndexedContainer(); 1187 1188 // create properties 1189 container.addContainerProperty(TableProperty.KEY, String.class, ""); 1190 container.addContainerProperty(TableProperty.DESCRIPTION, String.class, ""); 1191 container.addContainerProperty(TableProperty.DEFAULT, String.class, ""); 1192 1193 // add entries 1194 CmsXmlContentValueSequence messages = m_descContent.getValueSequence( 1195 "/" + Descriptor.N_MESSAGE, 1196 Descriptor.LOCALE); 1197 for (int i = 0; i < messages.getElementCount(); i++) { 1198 1199 String prefix = messages.getValue(i).getPath() + "/"; 1200 Object itemId = container.addItem(); 1201 Item item = container.getItem(itemId); 1202 String key = m_descContent.getValue(prefix + Descriptor.N_KEY, Descriptor.LOCALE).getStringValue(m_cms); 1203 item.getItemProperty(TableProperty.KEY).setValue(key); 1204 item.getItemProperty(TableProperty.DESCRIPTION).setValue( 1205 m_descContent.getValue(prefix + Descriptor.N_DESCRIPTION, Descriptor.LOCALE).getStringValue(m_cms)); 1206 item.getItemProperty(TableProperty.DEFAULT).setValue( 1207 m_descContent.getValue(prefix + Descriptor.N_DEFAULT, Descriptor.LOCALE).getStringValue(m_cms)); 1208 } 1209 1210 return container; 1211 1212 } 1213 1214 /** 1215 * Creates all propertyvfsbundle files for the currently loaded translations. 1216 * The method is used to convert xmlvfsbundle files into propertyvfsbundle files. 1217 * 1218 * @throws CmsIllegalArgumentException thrown if resource creation fails. 1219 * @throws CmsLoaderException thrown if the propertyvfsbundle type can't be read from the resource manager. 1220 * @throws CmsException thrown if creation, type retrieval or locking fails. 1221 */ 1222 private void createPropertyVfsBundleFiles() throws CmsIllegalArgumentException, CmsLoaderException, CmsException { 1223 1224 String bundleFileBasePath = m_sitepath + m_basename + "_"; 1225 for (Locale l : m_localizations.keySet()) { 1226 CmsResource res = m_cms.createResource( 1227 bundleFileBasePath + l.toString(), 1228 OpenCms.getResourceManager().getResourceType( 1229 CmsMessageBundleEditorTypes.BundleType.PROPERTY.toString())); 1230 m_bundleFiles.put(l, res); 1231 LockedFile file = LockedFile.lockResource(m_cms, res); 1232 file.setCreated(true); 1233 m_lockedBundleFiles.put(l, file); 1234 m_changedTranslations.add(l); 1235 } 1236 1237 } 1238 1239 /** 1240 * Creates the default editor state for editing a bundle with descriptor. 1241 * @return the default editor state for editing a bundle with descriptor. 1242 */ 1243 private EditorState getDefaultState() { 1244 1245 List<TableProperty> cols = new ArrayList<TableProperty>(1); 1246 cols.add(TableProperty.TRANSLATION); 1247 1248 return new EditorState(cols, false); 1249 } 1250 1251 /** 1252 * Returns a map with key property as key and item as value.<p> 1253 * 1254 * @return HashMap 1255 */ 1256 private Map<String, Item> getKeyItemMap() { 1257 1258 Map<String, Item> ret = new HashMap<String, Item>(); 1259 for (Object itemId : m_container.getItemIds()) { 1260 ret.put( 1261 m_container.getItem(itemId).getItemProperty(TableProperty.KEY).getValue().toString(), 1262 m_container.getItem(itemId)); 1263 } 1264 return ret; 1265 1266 } 1267 1268 /** 1269 * Reads the current properties for a language. If not already done, the properties are read from the respective file. 1270 * @param locale the locale for which the localization should be returned. 1271 * @return the properties. 1272 * @throws IOException thrown if reading the properties from a file fails. 1273 * @throws CmsException thrown if reading the properties from a file fails. 1274 */ 1275 private SortedProperties getLocalization(Locale locale) throws IOException, CmsException { 1276 1277 if (null == m_localizations.get(locale)) { 1278 switch (m_bundleType) { 1279 case PROPERTY: 1280 loadLocalizationFromPropertyBundle(locale); 1281 break; 1282 case XML: 1283 loadLocalizationFromXmlBundle(locale); 1284 break; 1285 case DESCRIPTOR: 1286 return null; 1287 default: 1288 break; 1289 } 1290 } 1291 return m_localizations.get(locale); 1292 } 1293 1294 /** 1295 * Returns the master mode's editor state for editing a bundle with descriptor. 1296 * @return the master mode's editor state for editing a bundle with descriptor. 1297 */ 1298 private EditorState getMasterState() { 1299 1300 List<TableProperty> cols = new ArrayList<TableProperty>(4); 1301 cols.add(TableProperty.KEY); 1302 cols.add(TableProperty.DESCRIPTION); 1303 cols.add(TableProperty.DEFAULT); 1304 cols.add(TableProperty.TRANSLATION); 1305 return new EditorState(cols, true); 1306 } 1307 1308 /** 1309 * Init the bundle type member variable. 1310 * @return the bundle type of the opened resource. 1311 */ 1312 private CmsMessageBundleEditorTypes.BundleType initBundleType() { 1313 1314 String resourceTypeName = OpenCms.getResourceManager().getResourceType(m_resource).getTypeName(); 1315 return CmsMessageBundleEditorTypes.BundleType.toBundleType(resourceTypeName); 1316 } 1317 1318 /** 1319 * Reads the bundle descriptor, sets m_desc and m_descContent. 1320 * @throws CmsXmlException thrown when unmarshalling fails. 1321 * @throws CmsException thrown when reading the resource fails or several bundle descriptors for the bundle exist. 1322 */ 1323 private void initDescriptor() throws CmsXmlException, CmsException { 1324 1325 if (m_bundleType.equals(CmsMessageBundleEditorTypes.BundleType.DESCRIPTOR)) { 1326 m_desc = m_resource; 1327 } else { 1328 //First try to read from same folder like resource, if it fails use CmsMessageBundleEditorTypes.getDescriptor() 1329 try { 1330 m_desc = m_cms.readResource(m_sitepath + m_basename + CmsMessageBundleEditorTypes.Descriptor.POSTFIX); 1331 } catch (CmsVfsResourceNotFoundException e) { 1332 m_desc = CmsMessageBundleEditorTypes.getDescriptor(m_cms, m_basename); 1333 } 1334 } 1335 unmarshalDescriptor(); 1336 1337 } 1338 1339 /** 1340 * Initializes the editor states for the different modes, depending on the type of the opened file. 1341 */ 1342 private void initEditorStates() { 1343 1344 m_editorState = new HashMap<CmsMessageBundleEditorTypes.EditMode, EditorState>(); 1345 List<TableProperty> cols = null; 1346 switch (m_bundleType) { 1347 case PROPERTY: 1348 case XML: 1349 if (hasDescriptor()) { // bundle descriptor is present, keys are not editable in default mode, maybe master mode is available 1350 m_editorState.put(CmsMessageBundleEditorTypes.EditMode.DEFAULT, getDefaultState()); 1351 if (hasMasterMode()) { // the bundle descriptor is editable 1352 m_editorState.put(CmsMessageBundleEditorTypes.EditMode.MASTER, getMasterState()); 1353 } 1354 } else { // no bundle descriptor given - implies no master mode 1355 cols = new ArrayList<TableProperty>(1); 1356 cols.add(TableProperty.KEY); 1357 cols.add(TableProperty.TRANSLATION); 1358 m_editorState.put(CmsMessageBundleEditorTypes.EditMode.DEFAULT, new EditorState(cols, true)); 1359 } 1360 break; 1361 case DESCRIPTOR: 1362 cols = new ArrayList<TableProperty>(3); 1363 cols.add(TableProperty.KEY); 1364 cols.add(TableProperty.DESCRIPTION); 1365 cols.add(TableProperty.DEFAULT); 1366 m_editorState.put(CmsMessageBundleEditorTypes.EditMode.DEFAULT, new EditorState(cols, true)); 1367 break; 1368 default: 1369 throw new IllegalArgumentException(); 1370 } 1371 1372 } 1373 1374 /** 1375 * Initializes the information on an available master mode. 1376 * @throws CmsException thrown if the write permission check on the bundle descriptor fails. 1377 */ 1378 private void initHasMasterMode() throws CmsException { 1379 1380 if (hasDescriptor() 1381 && m_cms.hasPermissions(m_desc, CmsPermissionSet.ACCESS_WRITE, false, CmsResourceFilter.ALL)) { 1382 m_hasMasterMode = true; 1383 } else { 1384 m_hasMasterMode = false; 1385 } 1386 } 1387 1388 /** 1389 * Initialize the key set for an xml bundle. 1390 */ 1391 private void initKeySetForXmlBundle() { 1392 1393 // consider only available locales 1394 for (Locale l : m_locales) { 1395 if (m_xmlBundle.hasLocale(l)) { 1396 Set<Object> keys = new HashSet<Object>(); 1397 for (I_CmsXmlContentValue msg : m_xmlBundle.getValueSequence("Message", l).getValues()) { 1398 String msgpath = msg.getPath(); 1399 keys.add(m_xmlBundle.getStringValue(m_cms, msgpath + "/Key", l)); 1400 } 1401 m_keyset.updateKeySet(null, keys); 1402 } 1403 } 1404 1405 } 1406 1407 /** 1408 * Initializes the locales that can be selected via the language switcher in the bundle editor. 1409 * @return the locales for which keys can be edited. 1410 */ 1411 private Collection<Locale> initLocales() { 1412 1413 Collection<Locale> locales = null; 1414 switch (m_bundleType) { 1415 case DESCRIPTOR: 1416 locales = new ArrayList<Locale>(1); 1417 locales.add(Descriptor.LOCALE); 1418 break; 1419 case XML: 1420 case PROPERTY: 1421 locales = OpenCms.getLocaleManager().getAvailableLocales(m_cms, m_resource); 1422 break; 1423 default: 1424 throw new IllegalArgumentException(); 1425 } 1426 return locales; 1427 1428 } 1429 1430 /** 1431 * Initialization necessary for editing a property bundle. 1432 * 1433 * @throws CmsLoaderException thrown if loading a bundle file fails. 1434 * @throws CmsException thrown if loading a bundle file fails. 1435 * @throws IOException thrown if loading a bundle file fails. 1436 */ 1437 private void initPropertyBundle() throws CmsLoaderException, CmsException, IOException { 1438 1439 for (Locale l : m_locales) { 1440 String filePath = m_sitepath + m_basename + "_" + l.toString(); 1441 CmsResource resource = null; 1442 if (m_cms.existsResource( 1443 filePath, 1444 CmsResourceFilter.requireType( 1445 OpenCms.getResourceManager().getResourceType(BundleType.PROPERTY.toString())))) { 1446 resource = m_cms.readResource(filePath); 1447 SortedProperties props = new SortedProperties(); 1448 CmsFile file = m_cms.readFile(resource); 1449 props.load( 1450 new InputStreamReader( 1451 new ByteArrayInputStream(file.getContents()), 1452 CmsFileUtil.getEncoding(m_cms, file))); 1453 m_keyset.updateKeySet(null, props.keySet()); 1454 m_bundleFiles.put(l, resource); 1455 } 1456 } 1457 1458 } 1459 1460 /** 1461 * Unmarshals the XML content and adds the file to the bundle files. 1462 * @throws CmsException thrown if reading the file or unmarshaling fails. 1463 */ 1464 private void initXmlBundle() throws CmsException { 1465 1466 CmsFile file = m_cms.readFile(m_resource); 1467 m_bundleFiles.put(null, m_resource); 1468 m_xmlBundle = CmsXmlContentFactory.unmarshal(m_cms, file); 1469 initKeySetForXmlBundle(); 1470 1471 } 1472 1473 /** 1474 * Check if values in the column "property" are written to the bundle files. 1475 * @param property the property id of the table column. 1476 * @return a flag, indicating if values of the table column are stored to the bundle files. 1477 */ 1478 private boolean isBundleProperty(Object property) { 1479 1480 return (property.equals(TableProperty.KEY) || property.equals(TableProperty.TRANSLATION)); 1481 } 1482 1483 /** 1484 * Check if values in the column "property" are written to the bundle descriptor. 1485 * @param property the property id of the table column. 1486 * @return a flag, indicating if values of the table column are stored to the bundle descriptor. 1487 */ 1488 private boolean isDescriptorProperty(Object property) { 1489 1490 return (getBundleType().equals(BundleType.DESCRIPTOR) 1491 || (hasDescriptor() 1492 && (property.equals(TableProperty.KEY) 1493 || property.equals(TableProperty.DEFAULT) 1494 || property.equals(TableProperty.DESCRIPTION)))); 1495 } 1496 1497 /** 1498 * Loads all localizations not already loaded. 1499 * @throws CmsException thrown if locking a file fails. 1500 * @throws UnsupportedEncodingException thrown if reading a file fails. 1501 * @throws IOException thrown if reading a file fails. 1502 */ 1503 private void loadAllRemainingLocalizations() throws CmsException, UnsupportedEncodingException, IOException { 1504 1505 if (!m_alreadyLoadedAllLocalizations) { 1506 // is only necessary for property bundles 1507 if (m_bundleType.equals(BundleType.PROPERTY)) { 1508 for (Locale l : m_locales) { 1509 if (null == m_localizations.get(l)) { 1510 CmsResource resource = m_bundleFiles.get(l); 1511 if (resource != null) { 1512 CmsFile file = m_cms.readFile(resource); 1513 m_bundleFiles.put(l, file); 1514 SortedProperties props = new SortedProperties(); 1515 props.load( 1516 new InputStreamReader( 1517 new ByteArrayInputStream(file.getContents()), 1518 CmsFileUtil.getEncoding(m_cms, file))); 1519 m_localizations.put(l, props); 1520 } 1521 } 1522 } 1523 } 1524 if (m_bundleType.equals(BundleType.XML)) { 1525 for (Locale l : m_locales) { 1526 if (null == m_localizations.get(l)) { 1527 loadLocalizationFromXmlBundle(l); 1528 } 1529 } 1530 } 1531 m_alreadyLoadedAllLocalizations = true; 1532 } 1533 1534 } 1535 1536 /** 1537 * Loads the propertyvfsbundle for the provided locale. 1538 * If the bundle file is not present, it will be created. 1539 * @param locale the locale for which the localization should be loaded 1540 * 1541 * @throws IOException thrown if loading fails. 1542 * @throws CmsException thrown if reading or creation fails. 1543 */ 1544 private void loadLocalizationFromPropertyBundle(Locale locale) throws IOException, CmsException { 1545 1546 // may throw exception again 1547 String sitePath = m_sitepath + m_basename + "_" + locale.toString(); 1548 CmsResource resource = null; 1549 if (m_cms.existsResource(sitePath)) { 1550 resource = m_cms.readResource(sitePath); 1551 if (!OpenCms.getResourceManager().getResourceType(resource).getTypeName().equals( 1552 CmsMessageBundleEditorTypes.BundleType.PROPERTY.toString())) { 1553 throw new CmsException( 1554 new CmsMessageContainer( 1555 Messages.get(), 1556 Messages.ERR_RESOURCE_HAS_WRONG_TYPE_2, 1557 locale.getDisplayName(), 1558 resource.getRootPath())); 1559 } 1560 } else { 1561 resource = m_cms.createResource( 1562 sitePath, 1563 OpenCms.getResourceManager().getResourceType( 1564 CmsMessageBundleEditorTypes.BundleType.PROPERTY.toString())); 1565 LockedFile lf = LockedFile.lockResource(m_cms, resource); 1566 lf.setCreated(true); 1567 m_lockedBundleFiles.put(locale, lf); 1568 } 1569 m_bundleFiles.put(locale, resource); 1570 SortedProperties props = new SortedProperties(); 1571 props.load( 1572 new InputStreamReader( 1573 new ByteArrayInputStream(m_cms.readFile(resource).getContents()), 1574 CmsFileUtil.getEncoding(m_cms, resource))); 1575 m_localizations.put(locale, props); 1576 1577 } 1578 1579 /** 1580 * Loads the localization for the current locale from a bundle of type xmlvfsbundle. 1581 * It assumes, the content has already been unmarshalled before. 1582 * @param locale the locale for which the localization should be loaded 1583 */ 1584 private void loadLocalizationFromXmlBundle(Locale locale) { 1585 1586 CmsXmlContentValueSequence messages = m_xmlBundle.getValueSequence("Message", locale); 1587 SortedProperties props = new SortedProperties(); 1588 if (null != messages) { 1589 for (I_CmsXmlContentValue msg : messages.getValues()) { 1590 String msgpath = msg.getPath(); 1591 props.put( 1592 m_xmlBundle.getStringValue(m_cms, msgpath + "/Key", locale), 1593 m_xmlBundle.getStringValue(m_cms, msgpath + "/Value", locale)); 1594 } 1595 } 1596 m_localizations.put(locale, props); 1597 } 1598 1599 /** 1600 * Locks all files of the currently edited bundle (that contain the provided key if it is not null). 1601 * @param key the key that must be contained in the localization to lock. If null, the files for all localizations are locked. 1602 * @throws CmsException thrown if locking fails. 1603 */ 1604 private void lockAllLocalizations(String key) throws CmsException { 1605 1606 for (Locale l : m_bundleFiles.keySet()) { 1607 if ((null == l) || m_localizations.get(l).containsKey(key)) { 1608 lockLocalization(l); 1609 } 1610 } 1611 } 1612 1613 /** 1614 * Locks the bundle descriptor. 1615 * @throws CmsException thrown if locking fails. 1616 */ 1617 private void lockDescriptor() throws CmsException { 1618 1619 if ((null == m_descFile) && (null != m_desc)) { 1620 m_descFile = LockedFile.lockResource(m_cms, m_desc); 1621 } 1622 } 1623 1624 /** 1625 * Locks the bundle file that contains the translation for the provided locale. 1626 * @param l the locale for which the bundle file should be locked. 1627 * @throws CmsException thrown if locking fails. 1628 */ 1629 private void lockLocalization(Locale l) throws CmsException { 1630 1631 if (null == m_lockedBundleFiles.get(l)) { 1632 LockedFile lf = LockedFile.lockResource(m_cms, m_bundleFiles.get(l)); 1633 m_lockedBundleFiles.put(l, lf); 1634 } 1635 1636 } 1637 1638 /** 1639 * Lock a file lazily, if a value that should be written to the file has changed. 1640 * @param propertyId the table column in which the value has changed (e.g., KEY, TRANSLATION, ...) 1641 * @throws CmsException thrown if locking fails. 1642 */ 1643 private void lockOnChange(Object propertyId) throws CmsException { 1644 1645 if (isDescriptorProperty(propertyId)) { 1646 lockDescriptor(); 1647 } else { 1648 Locale l = m_bundleType.equals(BundleType.PROPERTY) ? m_locale : null; 1649 lockLocalization(l); 1650 } 1651 1652 } 1653 1654 /** 1655 * Remove a key for all language versions. If a descriptor is present, the key is only removed in the descriptor. 1656 * 1657 * @param key the key to remove. 1658 * @return <code>true</code> if removing was successful, <code>false</code> otherwise. 1659 */ 1660 private boolean removeKeyForAllLanguages(String key) { 1661 1662 try { 1663 if (hasDescriptor()) { 1664 lockDescriptor(); 1665 } 1666 loadAllRemainingLocalizations(); 1667 lockAllLocalizations(key); 1668 } catch (CmsException | IOException e) { 1669 LOG.warn("Not able lock all localications for bundle.", e); 1670 return false; 1671 } 1672 if (!hasDescriptor()) { 1673 1674 for (Entry<Locale, SortedProperties> entry : m_localizations.entrySet()) { 1675 SortedProperties localization = entry.getValue(); 1676 if (localization.containsKey(key)) { 1677 localization.remove(key); 1678 m_changedTranslations.add(entry.getKey()); 1679 } 1680 } 1681 } 1682 return true; 1683 } 1684 1685 /** 1686 * Deletes the VFS XML bundle file. 1687 * @throws CmsException thrown if the delete operation fails. 1688 */ 1689 private void removeXmlBundleFile() throws CmsException { 1690 1691 lockLocalization(null); 1692 m_cms.deleteResource(m_resource, CmsResource.DELETE_PRESERVE_SIBLINGS); 1693 m_resource = null; 1694 1695 } 1696 1697 /** 1698 * Rename a key for all languages. 1699 * @param oldKey the key to rename 1700 * @param newKey the new key name 1701 * @return <code>true</code> if renaming was successful, <code>false</code> otherwise. 1702 */ 1703 private boolean renameKeyForAllLanguages(String oldKey, String newKey) { 1704 1705 try { 1706 loadAllRemainingLocalizations(); 1707 lockAllLocalizations(oldKey); 1708 if (hasDescriptor()) { 1709 lockDescriptor(); 1710 } 1711 } catch (CmsException | IOException e) { 1712 LOG.error(e.getLocalizedMessage(), e); 1713 return false; 1714 } 1715 for (Entry<Locale, SortedProperties> entry : m_localizations.entrySet()) { 1716 SortedProperties localization = entry.getValue(); 1717 if (localization.containsKey(oldKey)) { 1718 String value = localization.getProperty(oldKey); 1719 localization.remove(oldKey); 1720 localization.put(newKey, value); 1721 m_changedTranslations.add(entry.getKey()); 1722 } 1723 } 1724 if (hasDescriptor()) { 1725 CmsXmlContentValueSequence messages = m_descContent.getValueSequence( 1726 Descriptor.N_MESSAGE, 1727 Descriptor.LOCALE); 1728 for (int i = 0; i < messages.getElementCount(); i++) { 1729 1730 String prefix = messages.getValue(i).getPath() + "/"; 1731 String key = m_descContent.getValue(prefix + Descriptor.N_KEY, Descriptor.LOCALE).getStringValue(m_cms); 1732 if (key == oldKey) { 1733 m_descContent.getValue(prefix + Descriptor.N_KEY, Descriptor.LOCALE).setStringValue(m_cms, newKey); 1734 break; 1735 } 1736 } 1737 m_descriptorHasChanges = true; 1738 } 1739 m_keyset.renameKey(oldKey, newKey); 1740 return true; 1741 } 1742 1743 /** 1744 * Replaces the translations in an existing container with the translations for the provided locale. 1745 * @param locale the locale for which translations should be loaded. 1746 * @return <code>true</code> if replacing succeeded, <code>false</code> otherwise. 1747 */ 1748 private boolean replaceValues(Locale locale) { 1749 1750 try { 1751 SortedProperties localization = getLocalization(locale); 1752 if (hasDescriptor()) { 1753 for (Object itemId : m_container.getItemIds()) { 1754 Item item = m_container.getItem(itemId); 1755 String key = item.getItemProperty(TableProperty.KEY).getValue().toString(); 1756 Object value = localization.get(key); 1757 item.getItemProperty(TableProperty.TRANSLATION).setValue(null == value ? "" : value); 1758 } 1759 } else { 1760 m_container.removeAllItems(); 1761 Set<Object> keyset = m_keyset.getKeySet(); 1762 for (Object key : keyset) { 1763 Object itemId = m_container.addItem(); 1764 Item item = m_container.getItem(itemId); 1765 item.getItemProperty(TableProperty.KEY).setValue(key); 1766 Object value = localization.get(key); 1767 item.getItemProperty(TableProperty.TRANSLATION).setValue(null == value ? "" : value); 1768 } 1769 if (m_container.getItemIds().isEmpty()) { 1770 m_container.addItem(); 1771 } 1772 } 1773 return true; 1774 } catch (IOException | CmsException e) { 1775 // The problem should typically be a problem with locking or reading the file containing the translation. 1776 // This should be reported in the editor, if false is returned here. 1777 return false; 1778 } 1779 } 1780 1781 /** 1782 * Resets all information on changes. I.e., the editor assumes no changes are present anymore. 1783 * Call this method after a successful save action. 1784 */ 1785 private void resetChanges() { 1786 1787 m_changedTranslations.clear(); 1788 m_descriptorHasChanges = false; 1789 1790 } 1791 1792 /** 1793 * Saves the current translations from the container to the respective localization. 1794 */ 1795 private void saveLocalization() { 1796 1797 SortedProperties localization = new SortedProperties(); 1798 for (Object itemId : m_container.getItemIds()) { 1799 Item item = m_container.getItem(itemId); 1800 String key = item.getItemProperty(TableProperty.KEY).getValue().toString(); 1801 String value = item.getItemProperty(TableProperty.TRANSLATION).getValue().toString(); 1802 if (!(key.isEmpty() || value.isEmpty())) { 1803 localization.put(key, value); 1804 } 1805 } 1806 m_keyset.updateKeySet(m_localizations.get(m_locale).keySet(), localization.keySet()); 1807 m_localizations.put(m_locale, localization); 1808 1809 } 1810 1811 /** 1812 * Save the values to the bundle descriptor. 1813 * @throws CmsException thrown if saving fails. 1814 */ 1815 private void saveToBundleDescriptor() throws CmsException { 1816 1817 if (null != m_descFile) { 1818 m_removeDescriptorOnCancel = false; 1819 updateBundleDescriptorContent(); 1820 m_descFile.getFile().setContents(m_descContent.marshal()); 1821 m_cms.writeFile(m_descFile.getFile()); 1822 } 1823 } 1824 1825 /** 1826 * Saves messages to a propertyvfsbundle file. 1827 * 1828 * @throws CmsException thrown if writing to the file fails. 1829 */ 1830 private void saveToPropertyVfsBundle() throws CmsException { 1831 1832 for (Locale l : m_changedTranslations) { 1833 SortedProperties props = m_localizations.get(l); 1834 LockedFile f = m_lockedBundleFiles.get(l); 1835 if ((null != props) && (null != f)) { 1836 try { 1837 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 1838 Writer writer = new OutputStreamWriter(outputStream, f.getEncoding()); 1839 props.store(writer, null); 1840 byte[] contentBytes = outputStream.toByteArray(); 1841 CmsFile file = f.getFile(); 1842 file.setContents(contentBytes); 1843 String contentEncodingProperty = m_cms.readPropertyObject( 1844 file, 1845 CmsPropertyDefinition.PROPERTY_CONTENT_ENCODING, 1846 false).getValue(); 1847 if ((null == contentEncodingProperty) || !contentEncodingProperty.equals(f.getEncoding())) { 1848 m_cms.writePropertyObject( 1849 m_cms.getSitePath(file), 1850 new CmsProperty( 1851 CmsPropertyDefinition.PROPERTY_CONTENT_ENCODING, 1852 f.getEncoding(), 1853 f.getEncoding())); 1854 } 1855 m_cms.writeFile(file); 1856 } catch (IOException e) { 1857 LOG.error( 1858 Messages.get().getBundle().key( 1859 Messages.ERR_READING_FILE_UNSUPPORTED_ENCODING_2, 1860 f.getFile().getRootPath(), 1861 f.getEncoding()), 1862 e); 1863 1864 } 1865 } 1866 } 1867 } 1868 1869 /** 1870 * Saves messages to a xmlvfsbundle file. 1871 * 1872 * @throws CmsException thrown if writing to the file fails. 1873 */ 1874 private void saveToXmlVfsBundle() throws CmsException { 1875 1876 if (m_lockedBundleFiles.get(null) != null) { // If the file was not locked, no changes were made, i.e., storing is not necessary. 1877 for (Locale l : m_locales) { 1878 SortedProperties props = m_localizations.get(l); 1879 if (null != props) { 1880 if (m_xmlBundle.hasLocale(l)) { 1881 m_xmlBundle.removeLocale(l); 1882 } 1883 m_xmlBundle.addLocale(m_cms, l); 1884 int i = 0; 1885 List<Object> keys = new ArrayList<Object>(props.keySet()); 1886 Collections.sort(keys, CmsCaseInsensitiveStringComparator.getInstance()); 1887 for (Object key : keys) { 1888 if ((null != key) && !key.toString().isEmpty()) { 1889 String value = props.getProperty(key.toString()); 1890 if (!value.isEmpty()) { 1891 m_xmlBundle.addValue(m_cms, "Message", l, i); 1892 i++; 1893 m_xmlBundle.getValue("Message[" + i + "]/Key", l).setStringValue(m_cms, key.toString()); 1894 m_xmlBundle.getValue("Message[" + i + "]/Value", l).setStringValue(m_cms, value); 1895 } 1896 } 1897 } 1898 } 1899 CmsFile bundleFile = m_lockedBundleFiles.get(null).getFile(); 1900 bundleFile.setContents(m_xmlBundle.marshal()); 1901 m_cms.writeFile(bundleFile); 1902 } 1903 } 1904 } 1905 1906 /** Extract site path, base name and locale from the resource opened with the editor. */ 1907 private void setResourceInformation() { 1908 1909 String sitePath = m_cms.getSitePath(m_resource); 1910 int pathEnd = sitePath.lastIndexOf('/') + 1; 1911 String baseName = sitePath.substring(pathEnd); 1912 m_sitepath = sitePath.substring(0, pathEnd); 1913 switch (CmsMessageBundleEditorTypes.BundleType.toBundleType( 1914 OpenCms.getResourceManager().getResourceType(m_resource).getTypeName())) { 1915 case PROPERTY: 1916 String localeSuffix = CmsStringUtil.getLocaleSuffixForName(baseName); 1917 if ((null != localeSuffix) && !localeSuffix.isEmpty()) { 1918 baseName = baseName.substring( 1919 0, 1920 baseName.lastIndexOf(localeSuffix) - (1 /* cut off trailing underscore, too*/)); 1921 m_locale = CmsLocaleManager.getLocale(localeSuffix); 1922 } 1923 if ((null == m_locale) || !m_locales.contains(m_locale)) { 1924 m_switchedLocaleOnOpening = true; 1925 m_locale = m_locales.iterator().next(); 1926 } 1927 break; 1928 case XML: 1929 m_locale = OpenCms.getLocaleManager().getBestAvailableLocaleForXmlContent( 1930 m_cms, 1931 m_resource, 1932 m_xmlBundle); 1933 break; 1934 case DESCRIPTOR: 1935 m_basename = baseName.substring( 1936 0, 1937 baseName.length() - CmsMessageBundleEditorTypes.Descriptor.POSTFIX.length()); 1938 m_locale = new Locale("en"); 1939 break; 1940 default: 1941 throw new IllegalArgumentException( 1942 Messages.get().container( 1943 Messages.ERR_UNSUPPORTED_BUNDLE_TYPE_1, 1944 CmsMessageBundleEditorTypes.BundleType.toBundleType( 1945 OpenCms.getResourceManager().getResourceType(m_resource).getTypeName())).toString()); 1946 } 1947 m_basename = baseName; 1948 1949 } 1950 1951 /** 1952 * Unmarshals the descriptor content. 1953 * 1954 * @throws CmsXmlException thrown if the XML structure of the descriptor is wrong. 1955 * @throws CmsException thrown if reading the descriptor file fails. 1956 */ 1957 private void unmarshalDescriptor() throws CmsXmlException, CmsException { 1958 1959 if (null != m_desc) { 1960 1961 // unmarshal descriptor 1962 m_descContent = CmsXmlContentFactory.unmarshal(m_cms, m_cms.readFile(m_desc)); 1963 1964 // configure messages if wanted 1965 CmsProperty bundleProp = m_cms.readPropertyObject(m_desc, PROPERTY_BUNDLE_DESCRIPTOR_LOCALIZATION, true); 1966 if (!(bundleProp.isNullProperty() || bundleProp.getValue().trim().isEmpty())) { 1967 m_configuredBundle = bundleProp.getValue(); 1968 } 1969 } 1970 1971 } 1972 1973 /** 1974 * Update the descriptor content with values from the editor. 1975 * @throws CmsXmlException thrown if update fails due to a wrong XML structure (should never happen) 1976 */ 1977 private void updateBundleDescriptorContent() throws CmsXmlException { 1978 1979 if (m_descContent.hasLocale(Descriptor.LOCALE)) { 1980 m_descContent.removeLocale(Descriptor.LOCALE); 1981 } 1982 m_descContent.addLocale(m_cms, Descriptor.LOCALE); 1983 1984 int i = 0; 1985 Property<Object> descProp; 1986 String desc; 1987 Property<Object> defaultValueProp; 1988 String defaultValue; 1989 Map<String, Item> keyItemMap = getKeyItemMap(); 1990 List<String> keys = new ArrayList<String>(keyItemMap.keySet()); 1991 Collections.sort(keys, CmsCaseInsensitiveStringComparator.getInstance()); 1992 for (Object key : keys) { 1993 if ((null != key) && !key.toString().isEmpty()) { 1994 1995 m_descContent.addValue(m_cms, Descriptor.N_MESSAGE, Descriptor.LOCALE, i); 1996 i++; 1997 String messagePrefix = Descriptor.N_MESSAGE + "[" + i + "]/"; 1998 1999 m_descContent.getValue(messagePrefix + Descriptor.N_KEY, Descriptor.LOCALE).setStringValue( 2000 m_cms, 2001 (String)key); 2002 descProp = keyItemMap.get(key).getItemProperty(TableProperty.DESCRIPTION); 2003 if ((null != descProp) && (null != descProp.getValue())) { 2004 desc = descProp.getValue().toString(); 2005 m_descContent.getValue(messagePrefix + Descriptor.N_DESCRIPTION, Descriptor.LOCALE).setStringValue( 2006 m_cms, 2007 desc); 2008 } 2009 2010 defaultValueProp = keyItemMap.get(key).getItemProperty(TableProperty.DEFAULT); 2011 if ((null != defaultValueProp) && (null != defaultValueProp.getValue())) { 2012 defaultValue = defaultValueProp.getValue().toString(); 2013 m_descContent.getValue(messagePrefix + Descriptor.N_DEFAULT, Descriptor.LOCALE).setStringValue( 2014 m_cms, 2015 defaultValue); 2016 } 2017 2018 } 2019 } 2020 2021 } 2022 2023 /** 2024 * Clears all internal lock info. 2025 */ 2026 private void updateLockInformation() { 2027 2028 m_lockedBundleFiles.clear(); 2029 m_descFile = null; 2030 2031 } 2032}