001
014
015 package com.liferay.portlet.dynamicdatamapping.util;
016
017 import com.liferay.portal.kernel.exception.PortalException;
018 import com.liferay.portal.kernel.exception.SystemException;
019 import com.liferay.portal.kernel.log.Log;
020 import com.liferay.portal.kernel.log.LogFactoryUtil;
021 import com.liferay.portal.kernel.security.pacl.DoPrivileged;
022 import com.liferay.portal.kernel.util.HtmlUtil;
023 import com.liferay.portal.kernel.util.LocaleUtil;
024 import com.liferay.portal.kernel.util.StringBundler;
025 import com.liferay.portal.kernel.util.StringPool;
026 import com.liferay.portal.kernel.util.StringUtil;
027 import com.liferay.portal.kernel.util.Validator;
028 import com.liferay.portal.kernel.xml.Attribute;
029 import com.liferay.portal.kernel.xml.Document;
030 import com.liferay.portal.kernel.xml.DocumentException;
031 import com.liferay.portal.kernel.xml.Element;
032 import com.liferay.portal.kernel.xml.Node;
033 import com.liferay.portal.kernel.xml.SAXReaderUtil;
034 import com.liferay.portal.kernel.xml.XMLSchema;
035 import com.liferay.portal.kernel.xml.XPath;
036 import com.liferay.portlet.dynamicdatamapping.StructureDefinitionException;
037 import com.liferay.portlet.dynamicdatamapping.StructureDuplicateElementException;
038 import com.liferay.portlet.dynamicdatamapping.model.DDMStructure;
039 import com.liferay.portlet.dynamicdatamapping.model.DDMStructureConstants;
040 import com.liferay.portlet.dynamicdatamapping.storage.Field;
041 import com.liferay.portlet.dynamicdatamapping.storage.FieldConstants;
042 import com.liferay.portlet.dynamicdatamapping.storage.Fields;
043 import com.liferay.util.xml.XMLFormatter;
044
045 import java.io.IOException;
046 import java.io.Serializable;
047
048 import java.util.Date;
049 import java.util.HashSet;
050 import java.util.Iterator;
051 import java.util.List;
052 import java.util.Locale;
053 import java.util.Set;
054
055
059 @DoPrivileged
060 public class DDMXMLImpl implements DDMXML {
061
062 @Override
063 public String formatXML(Document document) {
064 try {
065 return document.formattedString(_XML_INDENT);
066 }
067 catch (IOException ioe) {
068 throw new SystemException(ioe);
069 }
070 }
071
072 @Override
073 public String formatXML(String xml) {
074
075
076
077
078
079 try {
080 xml = StringUtil.replace(xml, "&#", "[$SPECIAL_CHARACTER$]");
081 xml = XMLFormatter.toString(xml, _XML_INDENT);
082 xml = StringUtil.replace(xml, "[$SPECIAL_CHARACTER$]", "&#");
083
084 return xml;
085 }
086 catch (IOException ioe) {
087 throw new SystemException(ioe);
088 }
089 catch (org.dom4j.DocumentException de) {
090 throw new SystemException(de);
091 }
092 }
093
094 @Override
095 public Fields getFields(DDMStructure structure, String xml)
096 throws PortalException {
097
098 return getFields(structure, null, xml, null);
099 }
100
101 @Override
102 public Fields getFields(
103 DDMStructure structure, XPath xPath, String xml,
104 List<String> fieldNames)
105 throws PortalException {
106
107 Document document = null;
108
109 try {
110 document = SAXReaderUtil.read(xml);
111 }
112 catch (DocumentException de) {
113 if (_log.isDebugEnabled()) {
114 _log.debug(de.getMessage(), de);
115 }
116
117 return null;
118 }
119
120 if ((xPath != null) && !xPath.booleanValueOf(document)) {
121 return null;
122 }
123
124 Fields fields = new Fields();
125
126 Element rootElement = document.getRootElement();
127
128 List<Element> dynamicElementElements = rootElement.elements(
129 "dynamic-element");
130
131 for (Element dynamicElementElement : dynamicElementElements) {
132 String fieldName = dynamicElementElement.attributeValue("name");
133
134 if (!structure.hasField(fieldName) ||
135 ((fieldNames != null) && !fieldNames.contains(fieldName))) {
136
137 continue;
138 }
139
140 String fieldDataType = structure.getFieldDataType(fieldName);
141
142 List<Element> dynamicContentElements =
143 dynamicElementElement.elements("dynamic-content");
144
145 for (Element dynamicContentElement : dynamicContentElements) {
146 String fieldValue = dynamicContentElement.getText();
147
148 String languageId = dynamicContentElement.attributeValue(
149 "language-id");
150
151 Locale locale = LocaleUtil.fromLanguageId(languageId);
152
153 Serializable fieldValueSerializable =
154 FieldConstants.getSerializable(fieldDataType, fieldValue);
155
156 Field field = fields.get(fieldName);
157
158 if (field == null) {
159 field = new Field();
160
161 String defaultLanguageId =
162 dynamicElementElement.attributeValue(
163 "default-language-id");
164
165 Locale defaultLocale = LocaleUtil.fromLanguageId(
166 defaultLanguageId);
167
168 field.setDefaultLocale(defaultLocale);
169
170 field.setDDMStructureId(structure.getStructureId());
171 field.setName(fieldName);
172 field.setValue(locale, fieldValueSerializable);
173
174 fields.put(field);
175 }
176 else {
177 field.addValue(locale, fieldValueSerializable);
178 }
179 }
180 }
181
182 return fields;
183 }
184
185 @Override
186 public String getXML(Document document, Fields fields) {
187 Element rootElement = null;
188
189 try {
190 if (document != null) {
191 rootElement = document.getRootElement();
192 }
193 else {
194 document = SAXReaderUtil.createDocument();
195
196 rootElement = document.addElement("root");
197 }
198
199 Iterator<Field> itr = fields.iterator(true);
200
201 while (itr.hasNext()) {
202 Field field = itr.next();
203
204 List<Node> nodes = getElementsByName(document, field.getName());
205
206 for (Node node : nodes) {
207 document.remove(node);
208 }
209
210 appendField(rootElement, field);
211 }
212
213 return document.formattedString();
214 }
215 catch (IOException ioe) {
216 throw new SystemException(ioe);
217 }
218 }
219
220 @Override
221 public String getXML(Fields fields) {
222 return getXML(null, fields);
223 }
224
225 public void setXMLSchema(XMLSchema xmlSchema) {
226 _xmlSchema = xmlSchema;
227 }
228
229 @Override
230 public String updateXMLDefaultLocale(
231 String xml, Locale contentDefaultLocale,
232 Locale contentNewDefaultLocale) {
233
234 try {
235 if (LocaleUtil.equals(
236 contentDefaultLocale, contentNewDefaultLocale)) {
237
238 return xml;
239 }
240
241 Document document = SAXReaderUtil.read(xml);
242
243 Element rootElement = document.getRootElement();
244
245 Attribute availableLocalesAttribute = rootElement.attribute(
246 _AVAILABLE_LOCALES);
247
248 String contentNewDefaultLanguageId = LocaleUtil.toLanguageId(
249 contentNewDefaultLocale);
250
251 String availableLocalesAttributeValue =
252 availableLocalesAttribute.getValue();
253
254 if (!availableLocalesAttributeValue.contains(
255 contentNewDefaultLanguageId)) {
256
257 StringBundler sb = new StringBundler(3);
258
259 sb.append(availableLocalesAttribute.getValue());
260 sb.append(StringPool.COMMA);
261 sb.append(contentNewDefaultLanguageId);
262
263 availableLocalesAttribute.setValue(sb.toString());
264 }
265
266 Attribute defaultLocaleAttribute = rootElement.attribute(
267 _DEFAULT_LOCALE);
268
269 defaultLocaleAttribute.setValue(contentNewDefaultLanguageId);
270
271 fixElementsDefaultLocale(
272 rootElement, contentDefaultLocale, contentNewDefaultLocale);
273
274 return document.formattedString();
275 }
276 catch (DocumentException de) {
277 throw new SystemException(de);
278 }
279 catch (IOException ioe) {
280 throw new SystemException(ioe);
281 }
282 }
283
284 @Override
285 public String validateXML(String xml) throws PortalException {
286 try {
287 Document document = SAXReaderUtil.read(xml, _xmlSchema);
288
289 validate(document);
290
291 return document.asXML();
292 }
293 catch (StructureDefinitionException sde) {
294 throw sde;
295 }
296 catch (StructureDuplicateElementException sdee) {
297 throw sdee;
298 }
299 catch (Exception e) {
300 if (_log.isDebugEnabled()) {
301 _log.debug("Invalid XML content " + e.getMessage(), e);
302 }
303
304 throw new StructureDefinitionException(e);
305 }
306 }
307
308 protected void appendField(Element element, Field field) {
309 Element dynamicElementElement = element.addElement("dynamic-element");
310
311 dynamicElementElement.addAttribute(
312 "default-language-id",
313 LocaleUtil.toLanguageId(field.getDefaultLocale()));
314 dynamicElementElement.addAttribute("name", field.getName());
315
316 for (Locale locale : field.getAvailableLocales()) {
317 List<Serializable> values = field.getValues(locale);
318
319 for (Serializable value : values) {
320 Element dynamicContentElement =
321 dynamicElementElement.addElement("dynamic-content");
322
323 dynamicContentElement.addAttribute(
324 "language-id", LocaleUtil.toLanguageId(locale));
325
326 updateField(dynamicContentElement, value);
327 }
328 }
329 }
330
331 protected void fixElementsDefaultLocale(
332 Element element, Locale contentDefaultLocale,
333 Locale contentNewDefaultLocale) {
334
335 for (Element dynamicElementElement :
336 element.elements(_DYNAMIC_ELEMENT)) {
337
338 Element importMetaDataElement =
339 (Element)dynamicElementElement.selectSingleNode(
340 "meta-data[@locale='" + contentNewDefaultLocale.toString() +
341 "']");
342
343 if (importMetaDataElement == null) {
344 Element metaDataElement =
345 (Element)dynamicElementElement.selectSingleNode(
346 "meta-data[@locale='" +
347 contentDefaultLocale.toString() + "']");
348
349 Element copiedMetadataElement = metaDataElement.createCopy();
350
351 Attribute localeAttribute = copiedMetadataElement.attribute(
352 _LOCALE);
353
354 String contentNewDefaultLanguageId = LocaleUtil.toLanguageId(
355 contentNewDefaultLocale);
356
357 localeAttribute.setValue(contentNewDefaultLanguageId);
358
359 dynamicElementElement.add(copiedMetadataElement);
360 }
361
362 fixElementsDefaultLocale(
363 dynamicElementElement, contentDefaultLocale,
364 contentNewDefaultLocale);
365 }
366 }
367
368 protected List<Node> getElementsByName(Document document, String name) {
369 name = HtmlUtil.escapeXPathAttribute(name);
370
371 XPath xPathSelector = SAXReaderUtil.createXPath(
372 "
373
374 return xPathSelector.selectNodes(document);
375 }
376
377 protected void updateField(
378 Element dynamicContentElement, Serializable fieldValue) {
379
380 dynamicContentElement.clearContent();
381
382 if (fieldValue instanceof Date) {
383 Date valueDate = (Date)fieldValue;
384
385 fieldValue = valueDate.getTime();
386 }
387
388 String valueString = String.valueOf(fieldValue);
389
390 dynamicContentElement.addCDATA(valueString.trim());
391 }
392
393 protected void validate(Document document) throws Exception {
394 XPath xPathSelector = SAXReaderUtil.createXPath("
395
396 List<Node> nodes = xPathSelector.selectNodes(document);
397
398 Set<String> elementNames = new HashSet<>();
399
400 for (Node node : nodes) {
401 Element element = (Element)node;
402
403 String name = StringUtil.toLowerCase(
404 element.attributeValue("name"));
405
406 if (Validator.isNull(name)) {
407 throw new StructureDefinitionException(
408 "Element must have a name attribute " +
409 element.formattedString());
410 }
411
412 if (name.startsWith(DDMStructureConstants.XSD_NAME_RESERVED)) {
413 throw new StructureDefinitionException(
414 "Element name " + name + " is reserved");
415 }
416
417 if (elementNames.contains(name)) {
418 throw new StructureDuplicateElementException(
419 "Element with name " + name + " already exists");
420 }
421
422 elementNames.add(name);
423 }
424 }
425
426 private static final String _AVAILABLE_LOCALES = "available-locales";
427
428 private static final String _DEFAULT_LOCALE = "default-locale";
429
430 private static final String _DYNAMIC_ELEMENT = "dynamic-element";
431
432 private static final String _LOCALE = "locale";
433
434 private static final String _XML_INDENT = " ";
435
436 private static final Log _log = LogFactoryUtil.getLog(DDMXMLImpl.class);
437
438 private XMLSchema _xmlSchema;
439
440 }