/*
* Copyright (c) 2002-2012 Alibaba Group Holding Limited.
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.citrus.service.form.impl;
import static com.alibaba.citrus.service.form.FormConstant.*;
import static com.alibaba.citrus.util.Assert.*;
import static com.alibaba.citrus.util.BasicConstant.*;
import static com.alibaba.citrus.util.ObjectUtil.*;
import static com.alibaba.citrus.util.StringUtil.*;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Map;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
import com.alibaba.citrus.service.form.CustomErrorNotFoundException;
import com.alibaba.citrus.service.form.Field;
import com.alibaba.citrus.service.form.Group;
import com.alibaba.citrus.service.form.MessageContext;
import com.alibaba.citrus.service.form.Validator;
import com.alibaba.citrus.service.form.Validator.Context;
import com.alibaba.citrus.service.form.configuration.FieldConfig;
import com.alibaba.citrus.service.requestcontext.support.ValueListSupport;
import com.alibaba.citrus.util.ArrayUtil;
import com.alibaba.citrus.util.ObjectUtil;
import com.alibaba.citrus.util.StringEscapeUtil;
import com.alibaba.citrus.util.io.ByteArrayInputStream;
import com.alibaba.citrus.util.io.ByteArrayOutputStream;
import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 代表用户所提交表单中的一个field。
* <p>
* 注意:field对象不是线程安全的,不能被多线程共享。
* </p>
*
* @author Michael Zhou
*/
public class FieldImpl extends ValueListSupport implements Field {
private final static Logger log = LoggerFactory.getLogger(Field.class);
private final FieldConfig fieldConfig;
private final Group group;
private final String fieldKey;
private final MessageContext messageContext;
private boolean valid;
private String message;
private Attachment attachment;
/** 创建一个新field。 */
public FieldImpl(FieldConfig fieldConfig, Group group) {
super( //
assertNotNull(group, "group").getForm().getTypeConverter(), // converter
fieldConfig.getGroupConfig().getFormConfig().isConverterQuiet() // converter quiet
);
this.fieldConfig = assertNotNull(fieldConfig, "fieldConfig");
this.group = group;
this.fieldKey = group.getKey() + FIELD_KEY_SEPARATOR + fieldConfig.getKey();
this.messageContext = MessageContextFactory.newInstance(this);
}
/** 取得field的配置信息。 */
public FieldConfig getFieldConfig() {
return fieldConfig;
}
/** 取得包含此field的group。 */
public Group getGroup() {
return group;
}
/** 判定field是否通过验证。 */
public boolean isValid() {
return valid;
}
/**
* 取得在form中唯一代表该field的key。
* <p>
* 由固定前缀<code>"_fm"</code>,加上group名的缩写,加上group instance
* fieldKey,再加上field名的缩写构成。例如:<code>_fm.m._0.n</code>。
* </p>
*/
public String getKey() {
return fieldKey;
}
/**
* 取得在form中唯一代表该field的key,当用户提交的表单中未包含此field的信息时,取这个key的值作为该field的值。
* <p>
* 这对于checkbox之类的HTML控件特别有用。
* </p>
* <p>
* Key的格式为:<code>_fm.groupKey.instanceKey.fieldKey.absent</code>。
* </p>
*/
public String getAbsentKey() {
return getKey() + FORM_FIELD_ABSENT_KEY;
}
/**
* 取得在form中和当前field绑定的附件的key。
* <p>
* Key的格式为:<code>_fm.groupKey.instanceKey.fieldKey.attach</code>。
* </p>
*/
public String getAttachmentKey() {
return getKey() + FORM_FIELD_ATTACHMENT_KEY;
}
/** 取得出错信息。 */
public String getMessage() {
return message;
}
/**
* 设置错误信息,同时置<code>isValid()</code>为<code>false</code>。
* <p>
* 对于<code>isValid()</code>已经是<code>false</code>的字段,该方法无效(不覆盖现有的错误信息)
* </p>
* <p>
* id表示错误信息的ID,必须定义的form描述文件中。
* </p>
*/
public void setMessage(String id) {
setMessage(id, null);
}
/**
* 设置错误信息,同时置<code>isValid()</code>为<code>false</code>。
* <p>
* 对于<code>isValid()</code>已经是<code>false</code>的字段,该方法无效(不覆盖现有的错误信息)
* </p>
* <p>
* id表示错误信息的ID,必须定义的form描述文件中。params表示生成错误信息的参数表。
* </p>
*/
public void setMessage(String id, Map<String, ?> params) {
if (isValid()) {
boolean found = false;
for (Validator validator : getFieldConfig().getValidators()) {
if (isEquals(validator.getId(), id)) {
MessageContext expressionContext = MessageContextFactory.newInstance(this, validator);
expressionContext.putAll(params);
valid = false;
found = true;
message = validator.getMessage(new ValidatorContextImpl(expressionContext, this));
if (message == null) {
throw new CustomErrorNotFoundException("No message specified for error ID \"" + id + "\" in "
+ this);
}
break;
}
}
if (found) {
((GroupImpl) getGroup()).setValid(valid);
} else {
throw new CustomErrorNotFoundException("Specified error ID \"" + id + "\" was not found in " + this);
}
}
}
/** 初始化field值,但不验证表单字段。其中,<code>request</code>可以是<code>null</code>。 */
public void init(FormParameters request) {
valid = true;
attachment = null;
// request为null,表示是空表单(不是用户提交的),此时装载默认值
if (request == null) {
setValues(getFieldConfig().getDefaultValues());
} else {
setValues(request.getValues(getKey()));
// 如果field不存在,则检查absent fieldKey。
if (size() == 0) {
setValues(request.getValues(getAbsentKey()));
}
// 如果存在attachment,则装入之
String attachmentEncoded = trimToNull(request.getStringValue(getAttachmentKey()));
if (attachmentEncoded != null) {
attachment = new Attachment(attachmentEncoded);
}
}
}
/** 验证(或重新验证)字段。 */
protected void validate() {
valid = true;
for (Validator validator : getFieldConfig().getValidators()) {
MessageContext expressionContext = MessageContextFactory.newInstance(this, validator);
Context context = new ValidatorContextImpl(expressionContext, this);
boolean passed = validator.validate(context);
if (!passed) {
valid = false;
message = validator.getMessage(context);
break;
}
}
((GroupImpl) getGroup()).setValid(valid);
}
/** 取得field级别的错误信息表达式的context。 */
protected MessageContext getMessageContext() {
return messageContext;
}
/** 取得field name,相当于<code>getFieldConfig().getName()</code>。 */
public String getName() {
return getFieldConfig().getName();
}
/** 取得参数值,如果指定名称的参数不存在,则返回<code>""</code>。 */
@Override
public String getStringValue() {
return getStringValue(EMPTY_STRING);
}
/** 取得用来显示field的名称,相当于<code>getFieldConfig().getDisplayName()</code>。 */
public String getDisplayName() {
return getFieldConfig().getDisplayName();
}
/** 取得默认值,相当于<code>getFieldConfig().getDefaultValue()</code>。 */
public String getDefaultValue() {
return getFieldConfig().getDefaultValue();
}
/** 取得默认值,相当于<code>getFieldConfig().getDefaultValues()</code>。 */
public String[] getDefaultValues() {
return getFieldConfig().getDefaultValues();
}
/** 添加参数名/参数值。 */
@Override
public void addValue(Object value) {
if (getFieldConfig().isTrimming() && value instanceof String) {
value = trimToNull((String) value);
}
super.addValue(value);
}
/** 设置附件。 */
public Object getAttachment() {
return attachment == null ? null : attachment.getAttachment();
}
/** 设置编码后的附件。 */
public String getAttachmentEncoded() {
return attachment == null ? null : attachment.getAttachmentEncoded();
}
/** 是否包含附件? */
public boolean hasAttachment() {
return attachment != null && attachment.getAttachment() != null;
}
/**
* 设置附件。
* <p>
* 注意,当attachment已经存在时,该方法调用无效。欲强制设入,请先调用<code>clearAttachment()</code>。
* </p>
*/
public void setAttachment(Object attachment) {
if (this.attachment == null) {
this.attachment = new Attachment(attachment);
}
}
/** 清除附件。 */
public void clearAttachment() {
this.attachment = null;
}
/** 转换成易于阅读的字符串。 */
@Override
public String toString() {
return "Field[group: " + getGroup().getGroupConfig().getName() + "." + getGroup().getInstanceKey() + ", name: "
+ getFieldConfig().getName() + ", values: " + ObjectUtil.toString(getValues()) + ", valid: "
+ isValid() + "]";
}
/** 代表一个附件。 */
private static class Attachment {
private Object attachment;
private String attachmentEncoded;
public Attachment(Object attachment) {
setAttachment(attachment);
}
public Attachment(String attachmentEncoded) {
setAttachment(decode(attachmentEncoded));
}
public Object getAttachment() {
return attachment;
}
public void setAttachment(Object attachment) {
this.attachment = attachment;
this.attachmentEncoded = null;
}
public String getAttachmentEncoded() {
if (attachment != null && attachmentEncoded == null) {
attachmentEncoded = encode(attachment);
}
return attachmentEncoded;
}
private String encode(Object attachment) {
if (attachment == null) {
return null;
}
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 1. 序列化
// 2. 压缩
Deflater def = new Deflater(Deflater.BEST_COMPRESSION, false);
DeflaterOutputStream dos = new DeflaterOutputStream(baos, def);
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(dos);
oos.writeObject(attachment);
} finally {
if (oos != null) {
try {
oos.close();
} catch (IOException e) {
}
}
def.end();
}
byte[] plaintext = baos.toByteArray().toByteArray();
// 3. base64编码
return StringEscapeUtil.escapeURL(new String(Base64.encodeBase64(plaintext, false), "ISO-8859-1"));
} catch (Exception e) {
log.error("Failed to encode field attachment", e);
return "!Failure: " + e;
}
}
private Object decode(String attachmentEncoded) {
if (attachmentEncoded == null || attachmentEncoded.startsWith("!Failure:")) {
return null;
}
// 1. base64解码
byte[] plaintext = null;
try {
String encoded = StringEscapeUtil.unescapeURL(attachmentEncoded);
plaintext = Base64.decodeBase64(encoded.getBytes("ISO-8859-1"));
if (ArrayUtil.isEmptyArray(plaintext)) {
log.warn("Field attachment content is empty: " + encoded);
return null;
}
} catch (Exception e) {
log.warn("Failed to decode field attachment: " + e);
return null;
}
// 2. 解压缩
ByteArrayInputStream bais = new ByteArrayInputStream(plaintext);
Inflater inf = new Inflater(false);
InflaterInputStream iis = new InflaterInputStream(bais, inf);
// 3. 反序列化
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(iis);
return ois.readObject();
} catch (Exception e) {
log.warn("Failed to parse field attachment", e);
} finally {
if (ois != null) {
try {
ois.close();
} catch (IOException e) {
}
}
inf.end();
}
return null;
}
}
}