This is an automated email from the ASF dual-hosted git repository. aiceflower pushed a commit to branch release-0.9.4 in repository https://gitbox.apache.org/repos/asf/linkis.git
commit 8e845ad41a8a14553e2ff4b306068a889cc48b67 Author: alexzyWu <[email protected]> AuthorDate: Sun Feb 23 18:33:50 2020 +0800 #Fixes #299 --- .../CustomMultiPartFormDataTransformer.java | 311 +++++++++++++++++++++ .../core/formdata/FormDataTransformerFactory.java | 34 +++ .../core/formdata/FormStreamContent.java | 84 ++++++ .../formdata/MultiPartFormDataTransformer.java | 41 +++ 4 files changed, 470 insertions(+) diff --git a/datasourcemanager/server/src/main/java/com/webank/wedatasphere/linkis/datasourcemanager/core/formdata/CustomMultiPartFormDataTransformer.java b/datasourcemanager/server/src/main/java/com/webank/wedatasphere/linkis/datasourcemanager/core/formdata/CustomMultiPartFormDataTransformer.java new file mode 100644 index 0000000000..8d1b7edcb2 --- /dev/null +++ b/datasourcemanager/server/src/main/java/com/webank/wedatasphere/linkis/datasourcemanager/core/formdata/CustomMultiPartFormDataTransformer.java @@ -0,0 +1,311 @@ +/* + * Copyright 2019 WeBank + * 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.webank.wedatasphere.linkis.datasourcemanager.core.formdata; + +import com.webank.wedatasphere.linkis.common.exception.ErrorException; +import com.webank.wedatasphere.linkis.common.exception.WarnException; +import com.webank.wedatasphere.linkis.datasourcemanager.common.ServiceErrorCode; +import org.apache.commons.lang.StringUtils; +import org.glassfish.jersey.media.multipart.FormDataBodyPart; +import org.glassfish.jersey.media.multipart.FormDataContentDisposition; +import org.glassfish.jersey.media.multipart.FormDataMultiPart; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import javax.validation.ValidationException; +import javax.validation.Validator; +import javax.validation.groups.Default; +import javax.ws.rs.core.MediaType; +import java.io.ByteArrayInputStream; +import java.lang.reflect.*; +import java.util.*; + +/** + * Custom Transformer of multipart form + * @author alexwu + * 2020/02/12 + */ +public class CustomMultiPartFormDataTransformer implements MultiPartFormDataTransformer{ + private static final Logger LOG = LoggerFactory.getLogger(CustomMultiPartFormDataTransformer.class); + @Override + public <T> T transformToObject(FormDataMultiPart formData, Class<?> clazz, + Validator beanValidator) + throws ValidationException, ErrorException { + Field[] fields = clazz.getDeclaredFields(); + Map<String, Field> objectFieldMap = new HashMap<>(); + Object object; + try { + object = clazz.getConstructor().newInstance(); + //Put the bean fields information into map + for (Field field : fields) { + objectFieldMap.put(field.getName(), field); + } + Map<String, List<FormDataBodyPart>> formDataParts = formData.getFields(); + formDataParts.forEach((partName, bodyParts) -> { + List<String> stepFieldNames = Arrays.asList(StringUtils.split(partName.replace("[", ".["), ".")); + String rootFieldName = stepFieldNames.get(0); + Field field = objectFieldMap.get(rootFieldName); + if (null != field) { + try { + injectToObject(object, field, stepFieldNames, 0, transformBodyPartToObject(bodyParts)); + } catch (Exception e) { + throw new WarnException(ServiceErrorCode.TRANSFORM_FORM_ERROR.getValue(), "Fail to inject request parameter to object, message:[" + e.getMessage() +"]"); + } + } + }); + }catch(Exception e){ + LOG.error("Error in transform multipart from: " + e.getMessage()); + throw new ErrorException(ServiceErrorCode.TRANSFORM_FORM_ERROR.getValue(), "Error in transform multipart from: " + e.getMessage()); + } + if(null != beanValidator){ + Set<ConstraintViolation<T>> result = beanValidator.validate((T)object, Default.class); + if(result.size() > 0){ + throw new ConstraintViolationException(result); + } + } + return (T)object; + } + + + /** + * Transform body parts + * @param bodyParts body parts + * @return + */ + private Object transformBodyPartToObject(List<FormDataBodyPart> bodyParts){ + if(bodyParts.size() > 0){ + Object[] result = new Object[bodyParts.size()]; + for(int i = 0 ; i < bodyParts.size(); i++){ + //Get media type + FormDataBodyPart bodyPart = bodyParts.get(i); + MediaType mediaType = bodyPart.getMediaType(); + if(mediaType.getType().equals(MediaType.TEXT_PLAIN_TYPE.getType())){ + result[i] = bodyPart.getValue(); + }else { + //Think others as file + FormStreamContent streamContent = new FormStreamContent(); + FormDataContentDisposition disposition = bodyPart.getFormDataContentDisposition(); + streamContent.setStream(new ByteArrayInputStream(bodyPart.getValueAs(byte[].class))); + streamContent.setFileName(disposition.getFileName()); + streamContent.setCreateDate(disposition.getCreationDate()); + streamContent.setModifyDate(disposition.getModificationDate()); + streamContent.setParameters(disposition.getParameters()); + result[i] = streamContent; + } + } + return result.length > 1 ? result : result[0]; + } + return null; + } + + /** + * Inject value to object's field + * @param object object injected + * @param field field entity + * @param stepFieldNames step field name list + * @param step step index + * @param value actual value + * @throws Exception + */ + private void injectToObject(Object object, Field field, + List<String> stepFieldNames, int step, Object value) throws Exception { + Class<?> fieldType = field.getType(); + String fieldName = field.getName(); + if(step + 1 >= stepFieldNames.size()){ + if(fieldType.equals(String.class) || PrimitiveUtils.isPrimitive(fieldType)){ + setObjectField(object, field, PrimitiveUtils.primitiveTypeConverse(value, fieldType)); + } else if (fieldType.equals(Object.class)) { + setObjectField(object, field, value); + } else { + throw new IllegalAccessException("Cannot set value: " + value + " to object field: " + fieldName); + } + }else if(!PrimitiveUtils.isPrimitive(fieldType)){ + Object subObject = getObjectField(object, field); + if(null == subObject){ + if(fieldType.equals(Map.class)){ + subObject = HashMap.class.getConstructor().newInstance(); + }else if(fieldType.equals(List.class)){ + subObject = ArrayList.class.getConstructor().newInstance(); + }else { + subObject = fieldType.getConstructor().newInstance(); + } + setObjectField(object, field, subObject); + } + injectRecurse(subObject, field, stepFieldNames, step + 1, value); + } + + } + + /** + * Inject value to map structure + * @param mapObject map + * @param valueType value type + * @param stepFieldNames step field name list + * @param step step index + * @param value actual value + * @throws Exception + */ + private void injectToMap(Map mapObject, Class<?> valueType, + List<String> stepFieldNames, int step, Object value) throws Exception{ + String fieldName = stepFieldNames.get(step); + if(step + 1 >= stepFieldNames.size()){ + if(valueType.equals(String.class) || PrimitiveUtils.isPrimitive(valueType)){ + mapObject.put(fieldName, PrimitiveUtils.primitiveTypeConverse(value, valueType)); + }else if (valueType.equals(Object.class)){ + mapObject.put(fieldName, value); + }else{ + throw new IllegalAccessException("Cannot set value: " + value + " to map: " + stepFieldNames.get(step - 1)); + } + } + } + + /** + * Inject value to list structure + * @param listObject list object + * @param elementType element type + * @param stepFieldNames step field name list + * @param step step index + * @param value value + * @throws Exception + */ + private void injectToList(List listObject, Class<?> elementType, + List<String> stepFieldNames, int step, Object value) throws Exception{ + String fieldName = stepFieldNames.get(step); + if(step + 1 >= stepFieldNames.size() && fieldName.matches("\\[\\d+]")){ + int index = Integer.parseInt(fieldName.substring(1, fieldName.length() - 1)); + //expand list + int expand = index + 1 - listObject.size(); + while (expand-- > 0) { + listObject.add(null); + } + if(elementType.equals(String.class) || PrimitiveUtils.isPrimitive(elementType)){ + listObject.set(index, PrimitiveUtils.primitiveTypeConverse(value, elementType)); + }else if (elementType.equals(Object.class)){ + listObject.set(index, value); + }else{ + throw new IllegalAccessException("Cannot set value: " + value + " to array: " + stepFieldNames.get(step - 1)); + } + } + } + + /** + * Inject recursively + * @param subObject sub object + * @param field sub object's field + * @param stepFieldNames step field name list + * @param step step index + * @param value actual value + * @throws Exception + */ + private void injectRecurse(Object subObject, Field field, + List<String> stepFieldNames, int step, Object value) throws Exception{ + Class<?> fieldType = field.getType(); + if(fieldType.equals(Map.class)){ + Class<?>[] generic = getGenericTypes(field); + if(null == generic || generic[0].equals(String.class)){ + Class<?> valueType = null == generic ? String.class : generic[1]; + injectToMap((Map) subObject, valueType, stepFieldNames, step, value); + } + }else if(fieldType.equals(List.class)){ + Class<?>[] generic = getGenericTypes(field); + injectToList((List)subObject, generic == null ? String.class : generic[0], stepFieldNames, + step, value ); + }else{ + String nextFieldName = stepFieldNames.get(step); + Field nextField = subObject.getClass().getField(nextFieldName); + injectToObject(subObject, nextField, stepFieldNames, step, value); + } + } + + /** + * Get generic types + * @param field + * @return + */ + private Class<?>[] getGenericTypes(Field field){ + Type fc = field.getGenericType(); + if(fc instanceof ParameterizedType){ + Type[] types = ((ParameterizedType)fc).getActualTypeArguments(); + if(null != types && types.length > 0){ + Class<?>[] genericClazz = new Class<?>[types.length]; + for(int i = 0 ; i < genericClazz.length ; i++){ + genericClazz[i] = (Class<?>)types[i]; + } + return genericClazz; + } + } + return null; + } + + private void setObjectField(Object object, Field field, Object value) throws Exception{ + field.setAccessible(true); + field.set(object, value); + } + + private Object getObjectField(Object object, Field field) throws Exception{ + field.setAccessible(true); + return field.get(object); + } + + + /** + * Tool of primitive + */ + public static class PrimitiveUtils{ + public static Object primitiveTypeConverse(Object objValue, Class<?> type){ + if(type.equals(String.class) || null == objValue){ + return objValue; + } + String value = String.valueOf(objValue); + if(!type.isPrimitive()){ + try { + type = ((Class)type.getField("TYPE").get(null)); + } catch (Exception e) { + //ignore + } + } + switch (type.getSimpleName()) { + case "int": + return Integer.valueOf(value); + case "long": + return Long.valueOf(value); + case "short": + return Short.valueOf(value); + case "char": + return value.toCharArray()[0]; + case "float": + return Float.valueOf(value); + case "double": + return Double.valueOf(value); + case "byte": + return Byte.valueOf(value); + case "boolean": + return Boolean.valueOf(value); + default: + throw new RuntimeException("Type: " + type.getSimpleName() + " is not primitive"); + } + } + + public static boolean isPrimitive(Class<?> type){ + try { + return type.isPrimitive() || ((Class) type.getField("TYPE").get(null)).isPrimitive(); + }catch(Exception e){ + return false; + } + } + } + +} diff --git a/datasourcemanager/server/src/main/java/com/webank/wedatasphere/linkis/datasourcemanager/core/formdata/FormDataTransformerFactory.java b/datasourcemanager/server/src/main/java/com/webank/wedatasphere/linkis/datasourcemanager/core/formdata/FormDataTransformerFactory.java new file mode 100644 index 0000000000..be047706b5 --- /dev/null +++ b/datasourcemanager/server/src/main/java/com/webank/wedatasphere/linkis/datasourcemanager/core/formdata/FormDataTransformerFactory.java @@ -0,0 +1,34 @@ +/* + * Copyright 2019 WeBank + * 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.webank.wedatasphere.linkis.datasourcemanager.core.formdata; + +import java.util.concurrent.ConcurrentHashMap; + +/** + * Simple static factory + * + */ +public class FormDataTransformerFactory { + + private static final ConcurrentHashMap<String, MultiPartFormDataTransformer> + formDataTransformerStored = new ConcurrentHashMap<>(); + + /** + * Build custom implement + * @return + */ + public static MultiPartFormDataTransformer buildCustom(){ + return formDataTransformerStored.computeIfAbsent("custom", key -> new CustomMultiPartFormDataTransformer()); + } +} diff --git a/datasourcemanager/server/src/main/java/com/webank/wedatasphere/linkis/datasourcemanager/core/formdata/FormStreamContent.java b/datasourcemanager/server/src/main/java/com/webank/wedatasphere/linkis/datasourcemanager/core/formdata/FormStreamContent.java new file mode 100644 index 0000000000..4f5b318c3e --- /dev/null +++ b/datasourcemanager/server/src/main/java/com/webank/wedatasphere/linkis/datasourcemanager/core/formdata/FormStreamContent.java @@ -0,0 +1,84 @@ +/* + * Copyright 2019 WeBank + * 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.webank.wedatasphere.linkis.datasourcemanager.core.formdata; + +import java.io.InputStream; +import java.util.Date; +import java.util.Map; + +/** + * Stream content + * @author alexwu + * 2020/02/12 + */ +public class FormStreamContent { + private InputStream stream; + private String fileName; + + private long size; + + private Date createDate; + + private Date modifyDate; + + private Map<String, String> parameters; + + public InputStream getStream() { + return stream; + } + + public void setStream(InputStream stream) { + this.stream = stream; + } + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public long getSize() { + return size; + } + + public void setSize(long size) { + this.size = size; + } + + public Date getCreateDate() { + return createDate; + } + + public void setCreateDate(Date createDate) { + this.createDate = createDate; + } + + public Date getModifyDate() { + return modifyDate; + } + + public void setModifyDate(Date modifyDate) { + this.modifyDate = modifyDate; + } + + public Map<String, String> getParameters() { + return parameters; + } + + public void setParameters(Map<String, String> parameters) { + this.parameters = parameters; + } +} diff --git a/datasourcemanager/server/src/main/java/com/webank/wedatasphere/linkis/datasourcemanager/core/formdata/MultiPartFormDataTransformer.java b/datasourcemanager/server/src/main/java/com/webank/wedatasphere/linkis/datasourcemanager/core/formdata/MultiPartFormDataTransformer.java new file mode 100644 index 0000000000..73156e7af8 --- /dev/null +++ b/datasourcemanager/server/src/main/java/com/webank/wedatasphere/linkis/datasourcemanager/core/formdata/MultiPartFormDataTransformer.java @@ -0,0 +1,41 @@ +/* + * Copyright 2019 WeBank + * 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.webank.wedatasphere.linkis.datasourcemanager.core.formdata; + +import com.webank.wedatasphere.linkis.common.exception.ErrorException; +import org.glassfish.jersey.media.multipart.FormDataMultiPart; + +import javax.validation.ValidationException; +import javax.validation.Validator; + +/** + * Transformer of multipart form + * @author alexwu + * 2020/02/12 + */ +public interface MultiPartFormDataTransformer { + /** + * Transform the form data to object and validate its fields + * @param formData form data + * @param clazz clazz + * @param beanValidator validator + * @param <T> + * @return + * @throws ValidationException + * @throws ErrorException + */ + <T>T transformToObject(FormDataMultiPart formData, Class<?> clazz, Validator beanValidator) + throws ValidationException, ErrorException; + +} --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
