diff --git a/bom/pom.xml b/bom/pom.xml index 4be846de6..1d874d1a0 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -40,6 +40,7 @@ 0.6.12 1.5.9 0.4.1 + 0.16.0 1.53.0 @@ -519,6 +520,13 @@ + + + org.apache.fory + fory-core + ${fory.version} + + org.apache.curator diff --git a/codec/codec-sofa-fory/pom.xml b/codec/codec-sofa-fory/pom.xml new file mode 100644 index 000000000..dbb18cfb7 --- /dev/null +++ b/codec/codec-sofa-fory/pom.xml @@ -0,0 +1,46 @@ + + + 4.0.0 + + + com.alipay.sofa + sofa-rpc-codec + ${revision} + + + sofa-rpc-codec-sofa-fory + + + + com.alipay.sofa + sofa-rpc-codec-api + + + com.alipay.sofa + sofa-rpc-api + + + com.alipay.sofa + sofa-rpc-log + + + + + org.apache.fory + fory-core + + + + org.slf4j + slf4j-log4j12 + test + + + junit + junit + test + + + diff --git a/codec/codec-sofa-fory/src/main/java/com/alipay/sofa/rpc/codec/fory/ForySerializer.java b/codec/codec-sofa-fory/src/main/java/com/alipay/sofa/rpc/codec/fory/ForySerializer.java new file mode 100644 index 000000000..314360043 --- /dev/null +++ b/codec/codec-sofa-fory/src/main/java/com/alipay/sofa/rpc/codec/fory/ForySerializer.java @@ -0,0 +1,190 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.alipay.sofa.rpc.codec.fory; + +import com.alipay.sofa.common.config.SofaConfigs; +import com.alipay.sofa.rpc.codec.AbstractSerializer; +import com.alipay.sofa.rpc.codec.CustomSerializer; +import com.alipay.sofa.rpc.codec.common.BlackAndWhiteListFileLoader; +import com.alipay.sofa.rpc.codec.common.SerializeCheckStatus; +import com.alipay.sofa.rpc.codec.fory.serialize.SofaRequestForySerializer; +import com.alipay.sofa.rpc.codec.fory.serialize.SofaResponseForySerializer; +import com.alipay.sofa.rpc.common.config.RpcConfigKeys; +import com.alipay.sofa.rpc.core.exception.SofaRpcException; +import com.alipay.sofa.rpc.core.request.SofaRequest; +import com.alipay.sofa.rpc.core.response.SofaResponse; +import com.alipay.sofa.rpc.ext.Extension; +import com.alipay.sofa.rpc.transport.AbstractByteBuf; +import com.alipay.sofa.rpc.transport.ByteArrayWrapperByteBuf; +import org.apache.fory.Fory; +import org.apache.fory.ThreadLocalFory; +import org.apache.fory.ThreadSafeFory; +import org.apache.fory.config.Language; +import org.apache.fory.memory.MemoryBuffer; +import org.apache.fory.resolver.AllowListChecker; +import org.apache.fory.resolver.ClassResolver; + +import java.util.List; +import java.util.Map; + +import static org.apache.fory.config.CompatibleMode.COMPATIBLE; + +/** + * Apache Fory serializer for SOFARPC. + * Uses the new Apache Fory dependency (org.apache.fory:fory-core) + * instead of the legacy org.furyio:fury-core used by FurySerializer. + * + * @author sunhailin-Leo + * @see com.alipay.sofa.rpc.codec.fury.FurySerializer + */ +@Extension(value = "fory", code = 23) +public class ForySerializer extends AbstractSerializer { + + protected final ThreadSafeFory fory; + + private final String checkerMode = SofaConfigs.getOrDefault(RpcConfigKeys.SERIALIZE_CHECKER_MODE); + + public ForySerializer() { + fory = new ThreadLocalFory(classLoader -> { + Fory foryInstance = Fory.builder() + .withLanguage(Language.JAVA) + .withRefTracking(true) + .withCodegen(true) + .withNumberCompressed(true) + .withCompatibleMode(COMPATIBLE) + .requireClassRegistration(false) + .withClassLoader(classLoader) + .withAsyncCompilation(true) + .build(); + + // In Apache Fory, the security checker is set via + // classResolver.setClassChecker(ClassChecker). + ClassResolver classResolver = (ClassResolver) foryInstance.getTypeResolver(); + + if (checkerMode.equalsIgnoreCase(SerializeCheckStatus.DISABLE.name())) { + AllowListChecker noChecker = new AllowListChecker(AllowListChecker.CheckLevel.DISABLE); + classResolver.setTypeChecker(noChecker); + } else if (checkerMode.equalsIgnoreCase(SerializeCheckStatus.WARN.name())) { + AllowListChecker blackListChecker = new AllowListChecker(AllowListChecker.CheckLevel.WARN); + classResolver.setTypeChecker(blackListChecker); + blackListChecker.addListener(classResolver); + List blackList = BlackAndWhiteListFileLoader.SOFA_SERIALIZE_BLACK_LIST; + for (String key : blackList) { + blackListChecker.disallowClass(key + "*"); + } + } else if (checkerMode.equalsIgnoreCase(SerializeCheckStatus.STRICT.name())) { + // STRICT mode: apply both whitelist and blacklist + AllowListChecker blackAndWhiteListChecker = new AllowListChecker(AllowListChecker.CheckLevel.STRICT); + classResolver.setTypeChecker(blackAndWhiteListChecker); + blackAndWhiteListChecker.addListener(classResolver); + List whiteList = BlackAndWhiteListFileLoader.SOFA_SERIALIZER_WHITE_LIST; + for (String key : whiteList) { + blackAndWhiteListChecker.allowClass(key + "*"); + } + List blackList = BlackAndWhiteListFileLoader.SOFA_SERIALIZE_BLACK_LIST; + for (String key : blackList) { + blackAndWhiteListChecker.disallowClass(key + "*"); + } + } + + foryInstance.register(SofaRequest.class); + foryInstance.register(SofaResponse.class); + foryInstance.register(SofaRpcException.class); + return foryInstance; + }); + addCustomSerializer(SofaRequest.class, new SofaRequestForySerializer(fory)); + addCustomSerializer(SofaResponse.class, new SofaResponseForySerializer(fory)); + } + + @Override + public AbstractByteBuf encode(final Object object, final Map context) throws SofaRpcException { + if (object == null) { + throw buildSerializeError("Unsupported null message!"); + } + ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + try { + fory.setClassLoader(contextClassLoader); + CustomSerializer customSerializer = getObjCustomSerializer(object); + if (customSerializer != null) { + return customSerializer.encodeObject(object, context); + } else { + MemoryBuffer writeBuffer = MemoryBuffer.newHeapBuffer(32); + writeBuffer.writerIndex(0); + fory.serialize(writeBuffer, object); + return new ByteArrayWrapperByteBuf(writeBuffer.getBytes(0, writeBuffer.writerIndex())); + } + } catch (SofaRpcException e) { + throw e; + } catch (Exception e) { + throw buildSerializeError(e.getMessage(), e); + } finally { + fory.clearClassLoader(contextClassLoader); + } + } + + @Override + public Object decode(final AbstractByteBuf data, final Class clazz, final Map context) + throws SofaRpcException { + if (clazz == null) { + throw buildDeserializeError("Target class is null!"); + } + if (data.readableBytes() <= 0) { + throw buildDeserializeError("Deserialized array is empty."); + } + ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + try { + fory.setClassLoader(contextClassLoader); + CustomSerializer customSerializer = getCustomSerializer(clazz); + if (customSerializer != null) { + return customSerializer.decodeObject(data, context); + } else { + MemoryBuffer readBuffer = MemoryBuffer.fromByteArray(data.array()); + return fory.deserialize(readBuffer); + } + } catch (SofaRpcException e) { + throw e; + } catch (Exception e) { + throw buildDeserializeError(e.getMessage(), e); + } finally { + fory.clearClassLoader(contextClassLoader); + } + } + + @Override + public void decode(final AbstractByteBuf data, final Object template, final Map context) + throws SofaRpcException { + if (template == null) { + throw buildDeserializeError("template is null!"); + } + ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + try { + fory.setClassLoader(contextClassLoader); + CustomSerializer customSerializer = getObjCustomSerializer(template); + if (customSerializer != null) { + customSerializer.decodeObjectByTemplate(data, context, template); + } else { + throw buildDeserializeError("Only support decode from SofaRequest and SofaResponse template"); + } + } catch (SofaRpcException e) { + throw e; + } catch (Exception e) { + throw buildDeserializeError(e.getMessage(), e); + } finally { + fory.clearClassLoader(contextClassLoader); + } + } +} diff --git a/codec/codec-sofa-fory/src/main/java/com/alipay/sofa/rpc/codec/fory/serialize/SofaRequestForySerializer.java b/codec/codec-sofa-fory/src/main/java/com/alipay/sofa/rpc/codec/fory/serialize/SofaRequestForySerializer.java new file mode 100644 index 000000000..a033e22c2 --- /dev/null +++ b/codec/codec-sofa-fory/src/main/java/com/alipay/sofa/rpc/codec/fory/serialize/SofaRequestForySerializer.java @@ -0,0 +1,122 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.alipay.sofa.rpc.codec.fory.serialize; + +import com.alipay.sofa.rpc.codec.CustomSerializer; +import com.alipay.sofa.rpc.common.RemotingConstants; +import com.alipay.sofa.rpc.config.ConfigUniqueNameGenerator; +import com.alipay.sofa.rpc.core.exception.RpcErrorType; +import com.alipay.sofa.rpc.core.exception.SofaRpcException; +import com.alipay.sofa.rpc.core.request.SofaRequest; +import com.alipay.sofa.rpc.transport.AbstractByteBuf; +import com.alipay.sofa.rpc.transport.ByteArrayWrapperByteBuf; +import org.apache.fory.ThreadSafeFory; +import org.apache.fory.memory.MemoryBuffer; + +import java.util.Map; + +/** + * Custom serializer for {@link SofaRequest} using Apache Fory. + * + * @author sunhailin-Leo + */ +public class SofaRequestForySerializer implements CustomSerializer { + + private final ThreadSafeFory fory; + + public SofaRequestForySerializer(ThreadSafeFory fory) { + this.fory = fory; + } + + @Override + public AbstractByteBuf encodeObject(SofaRequest object, Map context) throws SofaRpcException { + try { + MemoryBuffer writeBuffer = MemoryBuffer.newHeapBuffer(32); + writeBuffer.writerIndex(0); + + boolean genericSerialize = context != null && + isGenericRequest(context.get(RemotingConstants.HEAD_GENERIC_TYPE)); + if (genericSerialize) { + // TODO support generic call + throw new SofaRpcException(RpcErrorType.CLIENT_SERIALIZE, "Generic call is not supported for now."); + } + fory.serialize(writeBuffer, object); + final Object[] args = object.getMethodArgs(); + fory.serialize(writeBuffer, args); + + return new ByteArrayWrapperByteBuf(writeBuffer.getBytes(0, writeBuffer.writerIndex())); + } catch (SofaRpcException e) { + throw e; + } catch (Exception e) { + throw new SofaRpcException(RpcErrorType.CLIENT_SERIALIZE, e.getMessage(), e); + } + } + + @Override + public SofaRequest decodeObject(AbstractByteBuf data, Map context) throws SofaRpcException { + MemoryBuffer readBuffer = MemoryBuffer.fromByteArray(data.array()); + try { + SofaRequest sofaRequest = (SofaRequest) fory.deserialize(readBuffer); + String targetServiceName = sofaRequest.getTargetServiceUniqueName(); + if (targetServiceName == null) { + throw new SofaRpcException(RpcErrorType.SERVER_DESERIALIZE, "Target service name of request is null!"); + } + String interfaceName = ConfigUniqueNameGenerator.getInterfaceName(targetServiceName); + sofaRequest.setInterfaceName(interfaceName); + final Object[] args = (Object[]) fory.deserialize(readBuffer); + sofaRequest.setMethodArgs(args); + return sofaRequest; + } catch (SofaRpcException e) { + throw e; + } catch (Exception e) { + throw new SofaRpcException(RpcErrorType.SERVER_DESERIALIZE, e.getMessage(), e); + } + } + + @Override + public void decodeObjectByTemplate(AbstractByteBuf data, Map context, SofaRequest template) + throws SofaRpcException { + if (data.readableBytes() <= 0) { + throw new SofaRpcException(RpcErrorType.SERVER_DESERIALIZE, "Deserialized array is empty."); + } + try { + MemoryBuffer readBuffer = MemoryBuffer.fromByteArray(data.array()); + SofaRequest tmp = (SofaRequest) fory.deserialize(readBuffer); + String targetServiceName = tmp.getTargetServiceUniqueName(); + if (targetServiceName == null) { + throw new SofaRpcException(RpcErrorType.SERVER_DESERIALIZE, "Target service name of request is null!"); + } + template.setMethodName(tmp.getMethodName()); + template.setMethodArgSigs(tmp.getMethodArgSigs()); + template.setTargetServiceUniqueName(tmp.getTargetServiceUniqueName()); + template.setTargetAppName(tmp.getTargetAppName()); + template.addRequestProps(tmp.getRequestProps()); + String interfaceName = ConfigUniqueNameGenerator.getInterfaceName(targetServiceName); + template.setInterfaceName(interfaceName); + final Object[] args = (Object[]) fory.deserialize(readBuffer); + template.setMethodArgs(args); + } catch (SofaRpcException e) { + throw e; + } catch (Exception e) { + throw new SofaRpcException(RpcErrorType.SERVER_DESERIALIZE, e.getMessage(), e); + } + } + + protected boolean isGenericRequest(String serializeType) { + return serializeType != null && !serializeType.equals(RemotingConstants.SERIALIZE_FACTORY_NORMAL); + } +} diff --git a/codec/codec-sofa-fory/src/main/java/com/alipay/sofa/rpc/codec/fory/serialize/SofaResponseForySerializer.java b/codec/codec-sofa-fory/src/main/java/com/alipay/sofa/rpc/codec/fory/serialize/SofaResponseForySerializer.java new file mode 100644 index 000000000..b022278a7 --- /dev/null +++ b/codec/codec-sofa-fory/src/main/java/com/alipay/sofa/rpc/codec/fory/serialize/SofaResponseForySerializer.java @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.alipay.sofa.rpc.codec.fory.serialize; + +import com.alipay.sofa.rpc.codec.CustomSerializer; +import com.alipay.sofa.rpc.common.RemotingConstants; +import com.alipay.sofa.rpc.core.exception.RpcErrorType; +import com.alipay.sofa.rpc.core.exception.SofaRpcException; +import com.alipay.sofa.rpc.core.response.SofaResponse; +import com.alipay.sofa.rpc.transport.AbstractByteBuf; +import com.alipay.sofa.rpc.transport.ByteArrayWrapperByteBuf; +import org.apache.fory.ThreadSafeFory; +import org.apache.fory.memory.MemoryBuffer; + +import java.util.Map; + +/** + * Custom serializer for {@link SofaResponse} using Apache Fory. + * + * @author sunhailin-Leo + */ +public class SofaResponseForySerializer implements CustomSerializer { + + private final ThreadSafeFory fory; + + public SofaResponseForySerializer(ThreadSafeFory fory) { + this.fory = fory; + } + + @Override + public AbstractByteBuf encodeObject(SofaResponse object, Map context) throws SofaRpcException { + try { + MemoryBuffer writeBuffer = MemoryBuffer.newHeapBuffer(32); + writeBuffer.writerIndex(0); + fory.serialize(writeBuffer, object); + return new ByteArrayWrapperByteBuf(writeBuffer.getBytes(0, writeBuffer.writerIndex())); + } catch (SofaRpcException e) { + throw e; + } catch (Exception e) { + // Fixed: was SERVER_DESERIALIZE, should be SERVER_SERIALIZE for encoding path + throw new SofaRpcException(RpcErrorType.SERVER_SERIALIZE, e.getMessage(), e); + } + } + + @Override + public SofaResponse decodeObject(AbstractByteBuf data, Map context) throws SofaRpcException { + MemoryBuffer readBuffer = MemoryBuffer.fromByteArray(data.array()); + try { + boolean genericSerialize = context != null && isGenericResponse( + context.get(RemotingConstants.HEAD_GENERIC_TYPE)); + if (genericSerialize) { + // TODO support generic call + // Fixed: was CLIENT_SERIALIZE, should be CLIENT_DESERIALIZE for decoding path + throw new SofaRpcException(RpcErrorType.CLIENT_DESERIALIZE, "Generic call is not supported for now."); + } + return (SofaResponse) fory.deserialize(readBuffer); + } catch (SofaRpcException e) { + throw e; + } catch (Exception e) { + // Fixed: was CLIENT_SERIALIZE, should be CLIENT_DESERIALIZE for decoding path + throw new SofaRpcException(RpcErrorType.CLIENT_DESERIALIZE, e.getMessage(), e); + } + } + + @Override + public void decodeObjectByTemplate(AbstractByteBuf data, Map context, SofaResponse template) + throws SofaRpcException { + if (data.readableBytes() <= 0) { + // Fixed: was CLIENT_SERIALIZE, should be CLIENT_DESERIALIZE for decoding path + throw new SofaRpcException(RpcErrorType.CLIENT_DESERIALIZE, "Deserialized array is empty."); + } + try { + MemoryBuffer readBuffer = MemoryBuffer.fromByteArray(data.array()); + boolean genericSerialize = context != null && isGenericResponse( + context.get(RemotingConstants.HEAD_GENERIC_TYPE)); + if (genericSerialize) { + // TODO support generic call + throw new SofaRpcException(RpcErrorType.CLIENT_DESERIALIZE, "Generic call is not supported for now."); + } else { + SofaResponse tmp = (SofaResponse) fory.deserialize(readBuffer); + template.setErrorMsg(tmp.getErrorMsg()); + template.setAppResponse(tmp.getAppResponse()); + template.setResponseProps(tmp.getResponseProps()); + } + } catch (SofaRpcException e) { + throw e; + } catch (Exception e) { + throw new SofaRpcException(RpcErrorType.CLIENT_DESERIALIZE, e.getMessage(), e); + } + } + + protected boolean isGenericResponse(String serializeType) { + return serializeType != null && serializeType.equals(RemotingConstants.SERIALIZE_FACTORY_GENERIC); + } +} diff --git a/codec/codec-sofa-fory/src/main/resources/META-INF/services/sofa-rpc/com.alipay.sofa.rpc.codec.Serializer b/codec/codec-sofa-fory/src/main/resources/META-INF/services/sofa-rpc/com.alipay.sofa.rpc.codec.Serializer new file mode 100644 index 000000000..89f43f4d1 --- /dev/null +++ b/codec/codec-sofa-fory/src/main/resources/META-INF/services/sofa-rpc/com.alipay.sofa.rpc.codec.Serializer @@ -0,0 +1 @@ +fory=com.alipay.sofa.rpc.codec.fory.ForySerializer diff --git a/codec/codec-sofa-fory/src/test/java/com/alipay/sofa/rpc/codec/fory/ForySerializerTest.java b/codec/codec-sofa-fory/src/test/java/com/alipay/sofa/rpc/codec/fory/ForySerializerTest.java new file mode 100644 index 000000000..86b6fc49e --- /dev/null +++ b/codec/codec-sofa-fory/src/test/java/com/alipay/sofa/rpc/codec/fory/ForySerializerTest.java @@ -0,0 +1,383 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.alipay.sofa.rpc.codec.fory; + +import com.alipay.sofa.rpc.codec.Serializer; +import com.alipay.sofa.rpc.codec.fory.model.blacklist.BlackListClass; +import com.alipay.sofa.rpc.codec.fory.model.none.NoneClassHasBlackClass; +import com.alipay.sofa.rpc.codec.fory.model.whitelist.DemoRequest; +import com.alipay.sofa.rpc.codec.fory.model.whitelist.DemoResponse; +import com.alipay.sofa.rpc.codec.fory.model.whitelist.DemoService; +import com.alipay.sofa.rpc.codec.fory.model.whitelist.WhiteClassHasBlackClass; +import com.alipay.sofa.rpc.common.RemotingConstants; +import com.alipay.sofa.rpc.common.RpcConstants; +import com.alipay.sofa.rpc.core.exception.SofaRpcException; +import com.alipay.sofa.rpc.core.invoke.SofaResponseCallback; +import com.alipay.sofa.rpc.core.request.RequestBase; +import com.alipay.sofa.rpc.core.request.SofaRequest; +import com.alipay.sofa.rpc.core.response.SofaResponse; +import com.alipay.sofa.rpc.ext.ExtensionLoaderFactory; +import com.alipay.sofa.rpc.transport.AbstractByteBuf; +import com.alipay.sofa.rpc.transport.ByteArrayWrapperByteBuf; +import org.apache.fory.Fory; +import org.apache.fory.config.Language; +import org.apache.fory.memory.MemoryBuffer; +import org.junit.Assert; +import org.junit.Test; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * Unit tests for {@link ForySerializer}. + * + * @author sunhailin-Leo + */ +public class ForySerializerTest { + + private final ForySerializer serializer = (ForySerializer) ExtensionLoaderFactory.getExtensionLoader( + Serializer.class).getExtension("fory"); + + @Test + public void encodeAndDecode() { + try { + serializer.encode(null, null); + Assert.fail(); + } catch (Exception e) { + // expected + } + + DemoRequest demoRequest = new DemoRequest(); + demoRequest.setName("a"); + AbstractByteBuf byteBuf = serializer.encode(demoRequest, null); + DemoRequest req2 = (DemoRequest) serializer.decode(byteBuf, DemoRequest.class, null); + Assert.assertEquals(demoRequest.getName(), req2.getName()); + + AbstractByteBuf data = serializer.encode("xxx", null); + String dst = (String) serializer.decode(data, String.class, null); + Assert.assertEquals("xxx", dst); + + try { + serializer.decode(data, null, null); + Assert.fail(); + } catch (Exception e) { + // expected + } + + try { + serializer.decode(data, "", null); + Assert.fail(); + } catch (Exception e) { + // expected + } + } + + @Test + public void testSofaRequest() throws Exception { + SofaRequest request = buildRequest(); + AbstractByteBuf data = serializer.encode(request, null); + + serializer.encode("123456", null); + SofaRequest decode = (SofaRequest) serializer.decode(data, SofaRequest.class, null); + assertEqualsSofaRequest(request, decode); + + SofaRequest newRequest = new SofaRequest(); + serializer.decode(data, newRequest, null); + assertEqualsSofaRequest(request, newRequest); + + // null request + newRequest = new SofaRequest(); + try { + serializer.decode(new ByteArrayWrapperByteBuf(new byte[0]), newRequest, null); + Assert.fail(); + } catch (Exception e) { + // expected + } + } + + @Test + public void testSofaResponse() throws Exception { + SofaResponse response = new SofaResponse(); + final DemoResponse demoAppResponse = new DemoResponse(); + demoAppResponse.setWord("result"); + response.setAppResponse(demoAppResponse); + AbstractByteBuf data = serializer.encode(response, null); + + try { + serializer.decode(data, null, null); + Assert.fail(); + } catch (Exception e) { + // expected + } + + try { + serializer.decode(data, new Object(), null); + Assert.fail(); + } catch (Exception e) { + // expected + } + + SofaResponse decode = (SofaResponse) serializer.decode(data, SofaResponse.class, null); + Assert.assertFalse(decode.isError()); + Assert.assertEquals(response.getAppResponse(), decode.getAppResponse()); + Assert.assertEquals("result", ((DemoResponse) decode.getAppResponse()).getWord()); + + // success response via template + SofaResponse newResponse = new SofaResponse(); + serializer.decode(data, newResponse, null); + Assert.assertFalse(newResponse.isError()); + Assert.assertEquals(response.getAppResponse(), newResponse.getAppResponse()); + Assert.assertEquals("result", ((DemoResponse) newResponse.getAppResponse()).getWord()); + + // null response + newResponse = new SofaResponse(); + try { + serializer.decode(new ByteArrayWrapperByteBuf(new byte[0]), newResponse, null); + Assert.fail(); + } catch (Exception e) { + // expected + } + + // error response + response = new SofaResponse(); + response.setErrorMsg("1233"); + data = serializer.encode(response, null); + newResponse = new SofaResponse(); + serializer.decode(data, newResponse, null); + Assert.assertTrue(newResponse.isError()); + Assert.assertEquals(response.getErrorMsg(), newResponse.getErrorMsg()); + } + + private SofaRequest buildRequest() throws NoSuchMethodException { + SofaRequest request = new SofaRequest(); + request.setInterfaceName(DemoService.class.getName()); + request.setMethodName("say"); + request.setMethod(DemoService.class.getMethod("say", DemoRequest.class)); + final DemoRequest demoRequest = new DemoRequest(); + demoRequest.setName("name"); + request.setMethodArgs(new Object[] { demoRequest }); + request.setMethodArgSigs(new String[] { DemoRequest.class.getCanonicalName() }); + request.setTargetServiceUniqueName(DemoService.class.getName() + ":1.0"); + request.setTargetAppName("targetApp"); + request.setSerializeType((byte) 23); + request.setTimeout(1024); + request.setInvokeType(RpcConstants.INVOKER_TYPE_SYNC); + Map map = new HashMap(); + map.put("a", "xxx"); + map.put("b", "yyy"); + request.addRequestProp(RemotingConstants.RPC_TRACE_NAME, map); + request.setSofaResponseCallback(new SofaResponseCallback() { + @Override + public void onAppResponse(Object appResponse, String methodName, RequestBase request) { + } + + @Override + public void onAppException(Throwable throwable, String methodName, RequestBase request) { + } + + @Override + public void onSofaException(SofaRpcException sofaException, String methodName, RequestBase request) { + } + }); + return request; + } + + private void assertEqualsSofaRequest(SofaRequest request, SofaRequest decode) { + // Fixed: assertEquals(expected, actual) — expected first + Assert.assertEquals(request.getInterfaceName(), decode.getInterfaceName()); + Assert.assertEquals(request.getMethodName(), decode.getMethodName()); + Assert.assertArrayEquals(request.getMethodArgSigs(), decode.getMethodArgSigs()); + Assert.assertEquals(request.getMethodArgs().length, decode.getMethodArgs().length); + Assert.assertEquals("name", ((DemoRequest) decode.getMethodArgs()[0]).getName()); + Assert.assertEquals(request.getTargetServiceUniqueName(), decode.getTargetServiceUniqueName()); + Assert.assertEquals(request.getTargetAppName(), decode.getTargetAppName()); + Assert.assertEquals(request.getRequestProp(RemotingConstants.RPC_TRACE_NAME), + decode.getRequestProp(RemotingConstants.RPC_TRACE_NAME)); + } + + @Test + public void testChecker() throws Exception { + // default fory checkMode is STRICT + WhiteClassHasBlackClass whiteClassNullBlackClass = new WhiteClassHasBlackClass(); + NoneClassHasBlackClass noneClassNullBlackClass = new NoneClassHasBlackClass(); + + BlackListClass blackListClass = new BlackListClass(); + WhiteClassHasBlackClass whiteClassHasBlackClass = new WhiteClassHasBlackClass(); + whiteClassHasBlackClass.setBlackListClass(blackListClass); + NoneClassHasBlackClass noneClassHasBlackClass = new NoneClassHasBlackClass(); + noneClassHasBlackClass.setBlackListClass(blackListClass); + + try { + serializer.encode(noneClassNullBlackClass, null); + Assert.fail(); + } catch (Exception e) { + // expected: NoneClass not in whitelist + } + + try { + serializer.encode(noneClassHasBlackClass, null); + Assert.fail(); + } catch (Exception e) { + // expected + } + + try { + serializer.encode(blackListClass, null); + Assert.fail(); + } catch (Exception e) { + // expected: BlackListClass not in whitelist + } + + serializer.encode(whiteClassNullBlackClass, null); + try { + serializer.encode(whiteClassHasBlackClass, null); + Assert.fail(); + } catch (Exception e) { + // expected: contains blacklist class + } + + // test WARN mode — wrapped in try-finally to guarantee system property cleanup + System.getProperties().put("sofa.rpc.codec.serialize.checkMode", "WARN"); + try { + ForySerializer warnForySerializer = new ForySerializer(); + + warnForySerializer.encode(noneClassNullBlackClass, null); + + try { + warnForySerializer.encode(noneClassHasBlackClass, null); + Assert.fail(); + } catch (Exception e) { + // expected + } + + try { + warnForySerializer.encode(blackListClass, null); + Assert.fail(); + } catch (Exception e) { + // expected + } + + warnForySerializer.encode(whiteClassNullBlackClass, null); + try { + warnForySerializer.encode(whiteClassHasBlackClass, null); + Assert.fail(); + } catch (Exception e) { + // expected + } + } finally { + System.getProperties().remove("sofa.rpc.codec.serialize.checkMode"); + } + + // test DISABLE mode — wrapped in try-finally to guarantee system property cleanup + System.getProperties().put("sofa.rpc.codec.serialize.checkMode", "DISABLE"); + try { + ForySerializer disableForySerializer = new ForySerializer(); + disableForySerializer.encode(noneClassNullBlackClass, null); + disableForySerializer.encode(noneClassHasBlackClass, null); + disableForySerializer.encode(blackListClass, null); + disableForySerializer.encode(whiteClassNullBlackClass, null); + disableForySerializer.encode(whiteClassHasBlackClass, null); + } finally { + System.getProperties().remove("sofa.rpc.codec.serialize.checkMode"); + } + } + + /** + * Builds a dedicated xlang-mode Fory instance for cross-language interop tests. + * This instance is separate from the JAVA-mode {@link ForySerializer}. + */ + private Fory buildXlangFory() { + final String typeTag = "com.alipay.sofa.rpc.codec.fory.model.whitelist.DemoRequest"; + Fory xlangFory = Fory.builder() + .withLanguage(Language.XLANG) + .withRefTracking(true) + .requireClassRegistration(true) + .build(); + xlangFory.register(DemoRequest.class, typeTag); + return xlangFory; + } + + /** + * Cross-language test: Java reads bytes produced by Python. + * + *

Prerequisite: Run {@code TestForyXlangInterop#test_python_to_java_serialize} in Python + * first. It serializes a {@code DemoRequest(name="hello from python")} with xlang mode and + * writes the bytes to {@code src/test/resources/xlang/fory_xlang_from_python.bin} in this + * Java project. Commit that file so it is available on the classpath at test time. + */ + @Test + public void testXlangReadFromPython() throws IOException { + final String classpathResource = "/xlang/fory_xlang_from_python.bin"; + Fory xlangFory = buildXlangFory(); + + try (java.io.InputStream pythonBytesStream = + ForySerializerTest.class.getResourceAsStream(classpathResource)) { + Assert.assertNotNull( + "Classpath resource not found: " + classpathResource + + ". Run TestForyXlangInterop#test_python_to_java_serialize in Python first," + + " then commit the file to src/test/resources/xlang/.", + pythonBytesStream); + + // Java 8 compatible: read all bytes from InputStream manually. + java.io.ByteArrayOutputStream byteBuffer = new java.io.ByteArrayOutputStream(); + byte[] chunk = new byte[4096]; + int bytesRead; + while ((bytesRead = pythonBytesStream.read(chunk)) != -1) { + byteBuffer.write(chunk, 0, bytesRead); + } + byte[] pythonBytes = byteBuffer.toByteArray(); + + Assert.assertTrue("Expected non-empty bytes from Python", pythonBytes.length > 0); + MemoryBuffer readBuffer = MemoryBuffer.fromByteArray(pythonBytes); + DemoRequest fromPython = (DemoRequest) xlangFory.deserialize(readBuffer); + Assert.assertEquals("hello from python", fromPython.getName()); + } + } + + /** + * Cross-language test: Java reads the pre-generated bytes produced by the Python test + * ({@code TestForyXlangInterop#test_python_to_java_serialize}) and deserializes them. + * + *

The bin file {@code src/test/resources/xlang/fory_xlang_from_java.bin} is committed to + * this project and loaded from the classpath, so no external dependency is needed. + * Expected deserialized value: {@code DemoRequest(name="hello from java")}. + */ + @Test + public void testXlangReadFromJava() throws IOException { + final String classpathResource = "/xlang/fory_xlang_from_java.bin"; + Fory xlangFory = buildXlangFory(); + + try (java.io.InputStream javaBytesStream = + ForySerializerTest.class.getResourceAsStream(classpathResource)) { + Assert.assertNotNull("Classpath resource not found: " + classpathResource, javaBytesStream); + + java.io.ByteArrayOutputStream buffer = new java.io.ByteArrayOutputStream(); + byte[] chunk = new byte[1024]; + int bytesRead; + while ((bytesRead = javaBytesStream.read(chunk)) != -1) { + buffer.write(chunk, 0, bytesRead); + } + byte[] javaBytes = buffer.toByteArray(); + + Assert.assertTrue("Expected non-empty bytes from Java", javaBytes.length > 0); + MemoryBuffer memoryBuffer = MemoryBuffer.fromByteArray(javaBytes); + DemoRequest fromJava = (DemoRequest) xlangFory.deserialize(memoryBuffer); + Assert.assertEquals("hello from java", fromJava.getName()); + } + } +} diff --git a/codec/codec-sofa-fory/src/test/java/com/alipay/sofa/rpc/codec/fory/model/blacklist/BlackListClass.java b/codec/codec-sofa-fory/src/test/java/com/alipay/sofa/rpc/codec/fory/model/blacklist/BlackListClass.java new file mode 100644 index 000000000..a4a1380b7 --- /dev/null +++ b/codec/codec-sofa-fory/src/test/java/com/alipay/sofa/rpc/codec/fory/model/blacklist/BlackListClass.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.alipay.sofa.rpc.codec.fory.model.blacklist; + +import java.io.Serializable; + +public class BlackListClass implements Serializable { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/codec/codec-sofa-fory/src/test/java/com/alipay/sofa/rpc/codec/fory/model/none/NoneClassHasBlackClass.java b/codec/codec-sofa-fory/src/test/java/com/alipay/sofa/rpc/codec/fory/model/none/NoneClassHasBlackClass.java new file mode 100644 index 000000000..ee0085935 --- /dev/null +++ b/codec/codec-sofa-fory/src/test/java/com/alipay/sofa/rpc/codec/fory/model/none/NoneClassHasBlackClass.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.alipay.sofa.rpc.codec.fory.model.none; + +import com.alipay.sofa.rpc.codec.fory.model.blacklist.BlackListClass; + +import java.io.Serializable; + +public class NoneClassHasBlackClass implements Serializable { + + private BlackListClass blackListClass; + + public BlackListClass getBlackListClass() { + return blackListClass; + } + + public void setBlackListClass(BlackListClass blackListClass) { + this.blackListClass = blackListClass; + } +} diff --git a/codec/codec-sofa-fory/src/test/java/com/alipay/sofa/rpc/codec/fory/model/whitelist/DemoRequest.java b/codec/codec-sofa-fory/src/test/java/com/alipay/sofa/rpc/codec/fory/model/whitelist/DemoRequest.java new file mode 100644 index 000000000..caab488f6 --- /dev/null +++ b/codec/codec-sofa-fory/src/test/java/com/alipay/sofa/rpc/codec/fory/model/whitelist/DemoRequest.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.alipay.sofa.rpc.codec.fory.model.whitelist; + +import java.io.Serializable; + +public class DemoRequest implements Serializable { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/codec/codec-sofa-fory/src/test/java/com/alipay/sofa/rpc/codec/fory/model/whitelist/DemoResponse.java b/codec/codec-sofa-fory/src/test/java/com/alipay/sofa/rpc/codec/fory/model/whitelist/DemoResponse.java new file mode 100644 index 000000000..70759bcd6 --- /dev/null +++ b/codec/codec-sofa-fory/src/test/java/com/alipay/sofa/rpc/codec/fory/model/whitelist/DemoResponse.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.alipay.sofa.rpc.codec.fory.model.whitelist; + +import java.io.Serializable; + +public class DemoResponse implements Serializable { + + private String word; + + public String getWord() { + return word; + } + + public void setWord(String word) { + this.word = word; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + DemoResponse that = (DemoResponse) obj; + return word != null ? word.equals(that.word) : that.word == null; + } + + @Override + public int hashCode() { + return word != null ? word.hashCode() : 0; + } +} diff --git a/codec/codec-sofa-fory/src/test/java/com/alipay/sofa/rpc/codec/fory/model/whitelist/DemoService.java b/codec/codec-sofa-fory/src/test/java/com/alipay/sofa/rpc/codec/fory/model/whitelist/DemoService.java new file mode 100644 index 000000000..1bdf480b5 --- /dev/null +++ b/codec/codec-sofa-fory/src/test/java/com/alipay/sofa/rpc/codec/fory/model/whitelist/DemoService.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.alipay.sofa.rpc.codec.fory.model.whitelist; + +public interface DemoService { + + String say(DemoRequest request); + + void sayVoid(DemoRequest request); + + DemoResponse sayResponse(DemoRequest request); +} diff --git a/codec/codec-sofa-fory/src/test/java/com/alipay/sofa/rpc/codec/fory/model/whitelist/WhiteClassHasBlackClass.java b/codec/codec-sofa-fory/src/test/java/com/alipay/sofa/rpc/codec/fory/model/whitelist/WhiteClassHasBlackClass.java new file mode 100644 index 000000000..796d22af6 --- /dev/null +++ b/codec/codec-sofa-fory/src/test/java/com/alipay/sofa/rpc/codec/fory/model/whitelist/WhiteClassHasBlackClass.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.alipay.sofa.rpc.codec.fory.model.whitelist; + +import com.alipay.sofa.rpc.codec.fory.model.blacklist.BlackListClass; + +import java.io.Serializable; + +public class WhiteClassHasBlackClass implements Serializable { + + private BlackListClass blackListClass; + + public BlackListClass getBlackListClass() { + return blackListClass; + } + + public void setBlackListClass(BlackListClass blackListClass) { + this.blackListClass = blackListClass; + } +} diff --git a/codec/codec-sofa-fory/src/test/resources/sofa-rpc/serialize_blacklist.txt b/codec/codec-sofa-fory/src/test/resources/sofa-rpc/serialize_blacklist.txt new file mode 100644 index 000000000..464b64878 --- /dev/null +++ b/codec/codec-sofa-fory/src/test/resources/sofa-rpc/serialize_blacklist.txt @@ -0,0 +1 @@ +com.alipay.sofa.rpc.codec.fory.model.blacklist. diff --git a/codec/codec-sofa-fory/src/test/resources/sofa-rpc/serialize_whitelist.txt b/codec/codec-sofa-fory/src/test/resources/sofa-rpc/serialize_whitelist.txt new file mode 100644 index 000000000..6525a81fd --- /dev/null +++ b/codec/codec-sofa-fory/src/test/resources/sofa-rpc/serialize_whitelist.txt @@ -0,0 +1,2 @@ +com.alipay.sofa.rpc.core. +com.alipay.sofa.rpc.codec.fory.model.whitelist. diff --git a/codec/codec-sofa-fory/src/test/resources/xlang/fory_xlang_from_java.bin b/codec/codec-sofa-fory/src/test/resources/xlang/fory_xlang_from_java.bin new file mode 100644 index 000000000..50d00754d Binary files /dev/null and b/codec/codec-sofa-fory/src/test/resources/xlang/fory_xlang_from_java.bin differ diff --git a/codec/codec-sofa-fory/src/test/resources/xlang/fory_xlang_from_python.bin b/codec/codec-sofa-fory/src/test/resources/xlang/fory_xlang_from_python.bin new file mode 100644 index 000000000..29539f8b4 Binary files /dev/null and b/codec/codec-sofa-fory/src/test/resources/xlang/fory_xlang_from_python.bin differ diff --git a/codec/pom.xml b/codec/pom.xml index 25814b811..98b576575 100644 --- a/codec/pom.xml +++ b/codec/pom.xml @@ -21,6 +21,7 @@ codec-sofa-hessian codec-msgpack codec-sofa-fury + codec-sofa-fory