/* * Copyright 2000-2013 Enonic AS * http://www.enonic.com/license */ package com.enonic.cms.core.content.contentdata.custom.support; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.fileupload.FileItem; import org.apache.commons.lang.StringUtils; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import com.enonic.esl.containers.ExtendedMap; import com.enonic.esl.io.FileUtil; import com.enonic.cms.core.content.ContentKey; import com.enonic.cms.core.content.contentdata.BinaryFileReadingException; import com.enonic.cms.core.content.contentdata.ContentDataParserInvalidDataException; import com.enonic.cms.core.content.contentdata.ContentDataParserUnsupportedTypeException; import com.enonic.cms.core.content.contentdata.custom.BinaryDataEntry; import com.enonic.cms.core.content.contentdata.custom.BooleanDataEntry; import com.enonic.cms.core.content.contentdata.custom.CustomContentData; import com.enonic.cms.core.content.contentdata.custom.DataEntry; import com.enonic.cms.core.content.contentdata.custom.DateDataEntry; import com.enonic.cms.core.content.contentdata.custom.FormItemsGroup; import com.enonic.cms.core.content.contentdata.custom.GroupDataEntry; import com.enonic.cms.core.content.contentdata.custom.contentkeybased.FileDataEntry; import com.enonic.cms.core.content.contentdata.custom.contentkeybased.ImageDataEntry; import com.enonic.cms.core.content.contentdata.custom.contentkeybased.RelatedContentDataEntry; import com.enonic.cms.core.content.contentdata.custom.relationdataentrylistbased.FilesDataEntry; import com.enonic.cms.core.content.contentdata.custom.relationdataentrylistbased.ImagesDataEntry; import com.enonic.cms.core.content.contentdata.custom.relationdataentrylistbased.RelatedContentsDataEntry; import com.enonic.cms.core.content.contentdata.custom.stringbased.HtmlAreaDataEntry; import com.enonic.cms.core.content.contentdata.custom.stringbased.SelectorDataEntry; import com.enonic.cms.core.content.contentdata.custom.stringbased.TextAreaDataEntry; import com.enonic.cms.core.content.contentdata.custom.stringbased.TextDataEntry; import com.enonic.cms.core.content.contentdata.custom.stringbased.UrlDataEntry; import com.enonic.cms.core.content.contentdata.custom.xmlbased.XmlDataEntry; import com.enonic.cms.core.content.contenttype.ContentTypeConfig; import com.enonic.cms.core.content.contenttype.CtySetConfig; import com.enonic.cms.core.content.contenttype.dataentryconfig.BinaryDataEntryConfig; import com.enonic.cms.core.content.contenttype.dataentryconfig.CheckboxDataEntryConfig; import com.enonic.cms.core.content.contenttype.dataentryconfig.DataEntryConfig; import com.enonic.cms.core.content.contenttype.dataentryconfig.DataEntryConfigType; import com.enonic.cms.core.content.contenttype.dataentryconfig.DateDataEntryConfig; import com.enonic.cms.core.content.contenttype.dataentryconfig.FileDataEntryConfig; import com.enonic.cms.core.content.contenttype.dataentryconfig.FilesDataEntryConfig; import com.enonic.cms.core.content.contenttype.dataentryconfig.HtmlAreaDataEntryConfig; import com.enonic.cms.core.content.contenttype.dataentryconfig.ImageDataEntryConfig; import com.enonic.cms.core.content.contenttype.dataentryconfig.ImagesDataEntryConfig; import com.enonic.cms.core.content.contenttype.dataentryconfig.RelatedContentDataEntryConfig; import com.enonic.cms.core.content.contenttype.dataentryconfig.TextAreaDataEntryConfig; import com.enonic.cms.core.content.contenttype.dataentryconfig.TextDataEntryConfig; /** * Created by rmy - Date: Jun 10, 2009 */ public class CustomContentDataFormParser { protected static final DateTimeFormatter DATE_FORMAT_FROM = DateTimeFormat.forPattern( "dd.MM.yyyy" ); private int binaryDataPlaceHolderCounter = 0; private ContentTypeConfig contentTypeConfig; private ExtendedMap formItems; private boolean parseOnlyCheckboxesMarkedAsInlcluded = false; private static final String UPLOADFILE_NEW_ITEM_SUFFIX = "_new"; public CustomContentDataFormParser( ContentTypeConfig contentTypeConfig, ExtendedMap formItems ) { this.contentTypeConfig = contentTypeConfig; this.formItems = formItems; } public void setParseOnlyCheckboxesMarkedAsInlcluded( boolean value ) { this.parseOnlyCheckboxesMarkedAsInlcluded = value; } public CustomContentData parseContentData() { List<DataEntry> parsedEntries = new ArrayList<DataEntry>(); for ( CtySetConfig setConfig : contentTypeConfig.getForm().getSetConfig() ) { parsedEntries.addAll( parseEntriesInBlock( setConfig ) ); } CustomContentData contentData = new CustomContentData( contentTypeConfig ); for ( DataEntry dataEntry : parsedEntries ) { contentData.add( dataEntry ); } return contentData; } private List<DataEntry> parseEntriesInBlock( CtySetConfig blockConfig ) { List<DataEntry> entries = new ArrayList<DataEntry>(); if ( isGroupTypeConfig( blockConfig ) ) { entries.addAll( parseEntriesInGroupBlock( blockConfig ) ); } else { entries.addAll( parseEntriesInNormalBlock( blockConfig ) ); } return entries; } private List<GroupDataEntry> parseEntriesInGroupBlock( CtySetConfig blockConfig ) { List<GroupDataEntry> groupDataEntries = new ArrayList<GroupDataEntry>(); FormItemsGroup formEntriesGroup = new FormItemsGroup( blockConfig.getName(), formItems ); final DataEntryParser dataEntryParser = new DataEntryParser(); for ( Integer groupIndex : formEntriesGroup.getGroupIndexes() ) { final Set<String> entryNamesAlreadyAdded = new HashSet<String>(); GroupDataEntry groupDataEntry = new GroupDataEntry( blockConfig.getName(), blockConfig.getGroupXPath(), groupIndex ); groupDataEntry.setConfig( blockConfig ); List<String> entriesInGroup = formEntriesGroup.getFormEntriesInGroup( groupIndex ); for ( String entryInGroup : entriesInGroup ) { String entryInGroupConfigName = formEntriesGroup.getFormEntryConfigName( entryInGroup ); DataEntryConfig dataEntryConfig = blockConfig.getInputConfig( entryInGroupConfigName ); if ( dataEntryConfig == null ) { dataEntryConfig = getBaseBinaryDataEntryConfigForGroup( entryInGroup, blockConfig, entryInGroupConfigName ); } if ( dataEntryConfig != null ) { DataEntry dataEntry = dataEntryParser.parseDataEntry( entryInGroup, dataEntryConfig ); if ( dataEntry != null ) { groupDataEntry.add( dataEntry ); entryNamesAlreadyAdded.add( entryInGroup ); } } } List<DataEntry> checkBoxesToAdd = parseMissingCheckBoxEntries( blockConfig, entryNamesAlreadyAdded, groupIndex ); for ( DataEntry checkBoxEntry : checkBoxesToAdd ) { groupDataEntry.add( checkBoxEntry ); } groupDataEntries.add( groupDataEntry ); } return groupDataEntries; } private List<DataEntry> parseEntriesInNormalBlock( final CtySetConfig blockConfig ) { final DataEntryParser dataEntryParser = new DataEntryParser(); final List<DataEntry> entries = new ArrayList<DataEntry>(); final Set<String> entryNamesAlreadyAdded = new HashSet<String>(); for ( Object formItemKey : formItems.keySet() ) { final String formInputName = (String) formItemKey; DataEntryConfig dataEntryConfig = getConfig( blockConfig, formInputName ); if ( dataEntryConfig == null ) { dataEntryConfig = getBaseBinaryDataEntryConfig( formInputName, blockConfig ); } if ( dataEntryConfig != null ) { DataEntry dataEntry = dataEntryParser.parseDataEntry( formInputName, dataEntryConfig ); if ( dataEntry != null ) { entries.add( dataEntry ); entryNamesAlreadyAdded.add( formInputName ); } } } entries.addAll( parseMissingCheckBoxEntries( blockConfig, entryNamesAlreadyAdded ) ); return entries; } private DataEntryConfig getBaseBinaryDataEntryConfigForGroup( String formInputName, CtySetConfig blockConfig, String entryInGroupConfigName ) { return doGetBaseBinaryDataEntryConfig( formInputName, blockConfig, entryInGroupConfigName ); } private DataEntryConfig getBaseBinaryDataEntryConfig( String formInputName, CtySetConfig blockConfig ) { return doGetBaseBinaryDataEntryConfig( formInputName, blockConfig, null ); } private DataEntryConfig doGetBaseBinaryDataEntryConfig( String formInputName, CtySetConfig blockConfig, String entryInGroupConfigName ) { DataEntryConfig dataEntryConfig; if ( !formInputName.endsWith( UPLOADFILE_NEW_ITEM_SUFFIX ) ) { return null; } String baseBinaryDataEntryKey = formInputName.substring( 0, formInputName.indexOf( UPLOADFILE_NEW_ITEM_SUFFIX ) ); boolean baseBinaryDataEntryKeyInFormItems = formItems.containsKey( baseBinaryDataEntryKey ); if ( baseBinaryDataEntryKeyInFormItems ) { return null; } String baseBinaryDataEntryConfigName = baseBinaryDataEntryKey; boolean isGroupItem = StringUtils.isNotEmpty( entryInGroupConfigName ); if ( isGroupItem ) { baseBinaryDataEntryConfigName = entryInGroupConfigName.substring( 0, entryInGroupConfigName.indexOf( UPLOADFILE_NEW_ITEM_SUFFIX ) ); } dataEntryConfig = getConfig( blockConfig, baseBinaryDataEntryConfigName ); if ( dataEntryConfig != null && dataEntryConfig.getType().equals( DataEntryConfigType.BINARY ) ) { return dataEntryConfig; } return null; } private List<DataEntry> parseMissingCheckBoxEntries( final CtySetConfig blockConfig, final Set<String> entryNamesAlreadyAdded ) { return parseMissingCheckBoxEntries( blockConfig, entryNamesAlreadyAdded, null ); } private List<DataEntry> parseMissingCheckBoxEntries( final CtySetConfig blockConfig, final Set<String> entryNamesAlreadyAdded, Integer groupIndex ) { List<DataEntry> entries = new ArrayList<DataEntry>(); final DataEntryParser dataEntryParser = new DataEntryParser(); dataEntryParser.treatNonExistingSubmittedValueAsNull = true; for ( DataEntryConfig dataEntryConfig : blockConfig.getInputConfigs() ) { if ( dataEntryConfig.getType() == DataEntryConfigType.CHECKBOX ) { boolean parsingWithinGroup = groupIndex != null; final String inputName; if ( parsingWithinGroup ) { inputName = createGroupDataEntryInputName( groupIndex, dataEntryConfig ); } else { inputName = dataEntryConfig.getName(); } final boolean checkboxEntryIsNotMissing = entryNamesAlreadyAdded.contains( inputName ); if ( checkboxEntryIsNotMissing ) { continue; } if ( parseOnlyCheckboxesMarkedAsInlcluded && !isCheckboxInputMarkedAsIncluded( inputName ) ) { continue; } DataEntry dataEntry = dataEntryParser.parseDataEntry( inputName, dataEntryConfig ); if ( dataEntry != null ) { entries.add( dataEntry ); } } } return entries; } private String createGroupDataEntryInputName( int groupIndex, DataEntryConfig dataEntryConfig ) { return dataEntryConfig.getSetConfig().getName() + '[' + groupIndex + ']' + '.' + dataEntryConfig.getName(); } private boolean isCheckboxInputMarkedAsIncluded( String inputName ) { String[] values = formItems.getStringArray( "_included_checkbox" ); for ( String value : values ) { if ( inputName.equals( value ) ) { return true; } } return false; } private DataEntryConfig getConfig( CtySetConfig blockConfig, String inputName ) { DataEntryConfig dataEntryConfig; dataEntryConfig = blockConfig.getInputConfig( inputName ); return dataEntryConfig; } private boolean isGroupTypeConfig( CtySetConfig contentTypeConfig ) { return contentTypeConfig.getGroupXPath() != null; } protected String getNextBinaryPlaceholder() { String placeHolder = "%" + binaryDataPlaceHolderCounter; binaryDataPlaceHolderCounter++; return placeHolder; } private class DataEntryParser { private boolean treatNonExistingSubmittedValueAsNull = false; private DataEntry parseDataEntry( String name, DataEntryConfig config ) { DataEntryConfigType type = config.getType(); switch ( type ) { case TEXT: return parseTextDataEntry( name, (TextDataEntryConfig) config ); case TEXT_AREA: return parseTextAreaDataEntry( name, (TextAreaDataEntryConfig) config ); case HTMLAREA: return parseHtmlAreaEntry( name, (HtmlAreaDataEntryConfig) config ); case DATE: return parseDateDataEntry( name, (DateDataEntryConfig) config ); case BINARY: return parseBinaryDataEntry( name, (BinaryDataEntryConfig) config ); case CHECKBOX: return parseCheckboxDataEntry( name, (CheckboxDataEntryConfig) config ); case IMAGES: return parseImagesDataEntry( name, (ImagesDataEntryConfig) config ); // Verify; If not isArrayFormItem case IMAGE: return parseImageDataEntry( name, (ImageDataEntryConfig) config ); case RELATEDCONTENT: return parseRelatedContentDataEntry( name, (RelatedContentDataEntryConfig) config ); case FILES: return parseFilesDataEntry( name, (FilesDataEntryConfig) config ); case FILE: return parseFileDataEntry( name, (FileDataEntryConfig) config ); case RADIOBUTTON: return parseRadioButtonDataEntry( name, config ); case URL: return parseURLDataEntry( name, config ); case XML: return parseXmlDataEntry( name, config ); case DROPDOWN: return parseDropdownDataEntry( name, config ); case MULTIPLE_CHOICE: return parseMultipleChoiceDataEntry(); case KEYWORDS: return null; } return null; } private TextDataEntry parseTextDataEntry( String name, TextDataEntryConfig config ) { String value = ensureNotNull( getSubmittedStringValue( name, formItems ) ); return new TextDataEntry( config, value ); } private TextAreaDataEntry parseTextAreaDataEntry( String name, TextAreaDataEntryConfig config ) { String value = ensureNotNull( getSubmittedStringValue( name, formItems ) ); return new TextAreaDataEntry( config, value ); } private HtmlAreaDataEntry parseHtmlAreaEntry( String name, HtmlAreaDataEntryConfig config ) { String value = ensureNotNull( getSubmittedStringValue( name, formItems ) ); return new HtmlAreaDataEntry( config, value ); } private DateDataEntry parseDateDataEntry( String name, DateDataEntryConfig config ) { String dateString = ensureNotNull( getSubmittedStringValue( name, formItems ) ); if ( StringUtils.isEmpty( dateString ) ) { return new DateDataEntry( config, null ); } Date date; try { date = DATE_FORMAT_FROM.parseDateTime( dateString ).toDate(); } catch ( final Exception e ) { String message = "Failed to parse date from input: " + dateString; throw new ContentDataParserInvalidDataException( message ); } return new DateDataEntry( config, date ); } private BinaryDataEntry parseBinaryDataEntry( String name, BinaryDataEntryConfig config ) { String newUploadFileItemKey = name; boolean isUploadFileFormItem = name.endsWith( UPLOADFILE_NEW_ITEM_SUFFIX ); if ( !isUploadFileFormItem ) { newUploadFileItemKey = getBinaryDataEntryUploadKey( name ); } FileItem fileItem = formItems.getFileItem( newUploadFileItemKey, null ); // This is done to support old forms submitting FileItems in the base-key if ( fileItem == null && !isUploadFileFormItem ) { fileItem = tryFetchFileItemFromBinaryDataUploadBaseKey( name ); } boolean newBinaryUploaded = fileItem != null; if ( newBinaryUploaded ) { String fileName = FileUtil.getFileName( fileItem ); byte[] binaryData = getBinaryData( fileItem ); return new BinaryDataEntry( config, getNextBinaryPlaceholder(), binaryData, fileName ); } String existingBinaryKey = ensureNotNull( getSubmittedStringValue( name, formItems ) ); boolean existingBinaryKeyGiven = StringUtils.isNotEmpty( existingBinaryKey ); if ( existingBinaryKeyGiven ) { return new BinaryDataEntry( config, new Integer( existingBinaryKey ) ); } return new BinaryDataEntry( config ); } private FileItem tryFetchFileItemFromBinaryDataUploadBaseKey( String name ) { FileItem fileItem; try { fileItem = formItems.getFileItem( name, null ); } catch ( ClassCastException e ) { fileItem = null; } return fileItem; } private String getBinaryDataEntryUploadKey( String name ) { return name + UPLOADFILE_NEW_ITEM_SUFFIX; } private BooleanDataEntry parseCheckboxDataEntry( String name, CheckboxDataEntryConfig config ) { String stringValue = ensureNotNull( getSubmittedStringValue( name, formItems ) ); Boolean value; if ( StringUtils.isNotEmpty( stringValue ) ) { value = true; } else { value = false; } return new BooleanDataEntry( config, value ); } private ImagesDataEntry parseImagesDataEntry( String name, ImagesDataEntryConfig config ) { ImagesDataEntry imagesDataEntry = new ImagesDataEntry( config ); if ( StringUtils.isEmpty( ensureNotNull( getSubmittedStringValue( name, formItems ) ) ) ) { return imagesDataEntry; } if ( isArrayFormItem( formItems, name ) ) { String[] images = (String[]) formItems.get( name ); String[] text = (String[]) formItems.get( name + "text", null ); for ( int i = 0; i < images.length; i++ ) { if ( images[i] == null || images[i].length() == 0 ) { continue; } ContentKey imageContentKey = new ContentKey( images[i] ); String imageText = ensureNotNull( text[i] ); ImageDataEntry imageDataEntry = new ImageDataEntry( config, imageContentKey, imageText ); imagesDataEntry.add( imageDataEntry ); } } else { String imageKeyStr = ensureNotNull( getSubmittedStringValue( name, formItems ) ); String imageText = ensureNotNull( formItems.getString( name + "text", null ) ); ContentKey imageContentKey = new ContentKey( imageKeyStr ); imagesDataEntry.add( new ImageDataEntry( config, imageContentKey, imageText ) ); } return imagesDataEntry; } private ImageDataEntry parseImageDataEntry( String name, ImageDataEntryConfig config ) { String imageString = ensureNotNull( getSubmittedStringValue( name, formItems ) ); ContentKey imageContentKey = null; if ( StringUtils.isNotEmpty( imageString ) ) { imageContentKey = new ContentKey( imageString ); } return new ImageDataEntry( config, imageContentKey ); } private DataEntry parseRelatedContentDataEntry( String name, RelatedContentDataEntryConfig config ) { if ( config.isMultiple() ) { RelatedContentsDataEntry relatedContentsDataEntry = new RelatedContentsDataEntry( config ); if ( StringUtils.isEmpty( getSubmittedStringValue( name, formItems ) ) ) { return relatedContentsDataEntry; } if ( isArrayFormItem( formItems, name ) ) { String[] contentKeyStrings = (String[]) formItems.get( name ); for ( String contentKeyString : contentKeyStrings ) { ContentKey contentKey = null; if ( StringUtils.isNotEmpty( contentKeyString ) ) { contentKey = new ContentKey( contentKeyString ); } relatedContentsDataEntry.add( new RelatedContentDataEntry( config, contentKey ) ); } } else { String contentKeyString = ensureNotNull( getSubmittedStringValue( name, formItems ) ); ContentKey contentKey = null; if ( StringUtils.isNotEmpty( contentKeyString ) ) { contentKey = new ContentKey( contentKeyString ); } relatedContentsDataEntry.add( new RelatedContentDataEntry( config, contentKey ) ); } return relatedContentsDataEntry; } else { String contentKeyString = getSubmittedStringValue( name, formItems ); ContentKey contentKey = null; if ( StringUtils.isNotEmpty( contentKeyString ) ) { contentKey = new ContentKey( contentKeyString ); } return new RelatedContentDataEntry( config, contentKey ); } } private FilesDataEntry parseFilesDataEntry( String name, FilesDataEntryConfig config ) { FilesDataEntry filesDataEntry = new FilesDataEntry( config ); if ( StringUtils.isEmpty( getSubmittedStringValue( name, formItems ) ) ) { return filesDataEntry; } if ( isArrayFormItem( formItems, name ) ) { String[] fileNames = (String[]) formItems.get( name ); for ( String fileName : fileNames ) { ContentKey contentKey = null; if ( StringUtils.isNotEmpty( fileName ) ) { contentKey = new ContentKey( fileName ); } filesDataEntry.add( new FileDataEntry( config, contentKey ) ); } } else { String fileName = ensureNotNull( getSubmittedStringValue( name, formItems ) ); ContentKey contentKey = null; if ( StringUtils.isNotEmpty( fileName ) ) { contentKey = new ContentKey( fileName ); } filesDataEntry.add( new FileDataEntry( config, contentKey ) ); } return filesDataEntry; } private FileDataEntry parseFileDataEntry( String name, FileDataEntryConfig config ) { String contentKeyString = getSubmittedStringValue( name, formItems ); ContentKey contentKey = null; if ( StringUtils.isNotEmpty( contentKeyString ) ) { contentKey = new ContentKey( contentKeyString ); } return new FileDataEntry( config, contentKey ); } private UrlDataEntry parseURLDataEntry( String name, DataEntryConfig config ) { String urlString = ensureNotNull( getSubmittedStringValue( name, formItems ) ); return new UrlDataEntry( config, urlString ); } private XmlDataEntry parseXmlDataEntry( String name, DataEntryConfig config ) { String xmlString = ensureNotNull( getSubmittedStringValue( name, formItems ) ); return new XmlDataEntry( config, xmlString ); } private DataEntry parseDropdownDataEntry( String name, DataEntryConfig config ) { String selectorValue = ensureNotNull( getSubmittedStringValue( name, formItems ) ); return new SelectorDataEntry( config, selectorValue ); } private SelectorDataEntry parseRadioButtonDataEntry( String name, DataEntryConfig config ) { String selectorValue = ensureNotNull( getSubmittedStringValue( name, formItems ) ); return new SelectorDataEntry( config, selectorValue ); } private DataEntry parseMultipleChoiceDataEntry() { // Det har blitt besluttet at multiple choice ikke skal støttes i user services. (JVS, TSI og JSI, 26. nov, 2009) String message = "Elements of type multiple choice is not supported"; throw new ContentDataParserUnsupportedTypeException( message ); } private boolean isArrayFormItem( Map formItems, String formItemObjectKey ) { if ( formItemObjectKey == null ) { return false; } if ( !formItems.containsKey( formItemObjectKey ) ) { return false; } return formItems.get( formItemObjectKey ).getClass() == String[].class; } private byte[] getBinaryData( FileItem fileItem ) { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); InputStream fileInputStream = null; try { fileInputStream = fileItem.getInputStream(); byte[] buffer = new byte[1024 * 16]; int size; while ( ( size = fileInputStream.read( buffer ) ) > 0 ) { byteArrayOutputStream.write( buffer, 0, size ); } } catch ( IOException e ) { String message = "Failed to read binary data: " + fileItem.getName(); throw new BinaryFileReadingException( message, e ); } finally { try { if ( fileInputStream != null ) { fileInputStream.close(); } } catch ( IOException ioe ) { String message = "Failed to close file input stream: %t"; throw new BinaryFileReadingException( message, ioe ); } } return byteArrayOutputStream.toByteArray(); } private String ensureNotNull( String value ) { return value == null ? "" : value; } private String getSubmittedStringValue( String name, ExtendedMap formItems ) { if ( treatNonExistingSubmittedValueAsNull ) { return formItems.getString( name, null ); } else { return formItems.getString( name ); } } } }