diff options
Diffstat (limited to '')
10 files changed, 1991 insertions, 0 deletions
diff --git a/sca-java-2.x/tags/2.0-M5.1/modules/binding-rest-runtime/src/main/java/org/apache/tuscany/sca/binding/rest/provider/DataBindingJAXRSProvider.java b/sca-java-2.x/tags/2.0-M5.1/modules/binding-rest-runtime/src/main/java/org/apache/tuscany/sca/binding/rest/provider/DataBindingJAXRSProvider.java new file mode 100644 index 0000000000..1ccdb868d7 --- /dev/null +++ b/sca-java-2.x/tags/2.0-M5.1/modules/binding-rest-runtime/src/main/java/org/apache/tuscany/sca/binding/rest/provider/DataBindingJAXRSProvider.java @@ -0,0 +1,203 @@ +/* + * 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 org.apache.tuscany.sca.binding.rest.provider; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.Reader; +import java.io.StringWriter; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; + +import javax.activation.DataSource; +import javax.jws.WebParam; +import javax.jws.WebResult; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.ext.Provider; +import javax.xml.namespace.QName; + +import org.apache.tuscany.sca.core.ExtensionPointRegistry; +import org.apache.tuscany.sca.core.UtilityExtensionPoint; +import org.apache.tuscany.sca.databinding.DataBindingExtensionPoint; +import org.apache.tuscany.sca.databinding.Mediator; +import org.apache.tuscany.sca.interfacedef.DataType; +import org.apache.tuscany.sca.interfacedef.impl.DataTypeImpl; +import org.apache.tuscany.sca.interfacedef.util.XMLType; + +/** + * A JAX-RS provider that leverages Tuscany's databinding framework to handle read/write + * for JAX-RS runtime + */ +@Provider +public abstract class DataBindingJAXRSProvider { + protected DataBindingExtensionPoint dataBindingExtensionPoint; + protected Mediator mediator; + + public DataBindingJAXRSProvider(ExtensionPointRegistry registry) { + this.dataBindingExtensionPoint = registry.getExtensionPoint(DataBindingExtensionPoint.class); + UtilityExtensionPoint utilities = registry.getExtensionPoint(UtilityExtensionPoint.class); + this.mediator = utilities.getUtility(Mediator.class); + } + + protected <A extends Annotation> A getAnnotation(Annotation[] annotations, Class<A> type) { + for (Annotation a : annotations) { + if (a.annotationType() == type) { + return type.cast(a); + } + } + return null; + } + + protected void introspectAnnotations(Annotation[] annotations, DataType targetDataType) { + WebResult result = getAnnotation(annotations, WebResult.class); + if (result != null) { + QName name = new QName(result.targetNamespace(), result.name()); + targetDataType.setLogical(new XMLType(name, null)); + } + + WebParam param = getAnnotation(annotations, WebParam.class); + if (param != null) { + QName name = new QName(param.targetNamespace(), param.name()); + targetDataType.setLogical(new XMLType(name, null)); + } + } + + protected DataType createDataType(Class<?> type, Type genericType) { + DataType dataType = new DataTypeImpl(null, type, type, genericType); + dataBindingExtensionPoint.introspectType(dataType, null); + return dataType; + } + + protected boolean supports(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { + // Some media types have parameters + mediaType = new MediaType(mediaType.getType(), mediaType.getSubtype()); + return MediaType.APPLICATION_JSON_TYPE.isCompatible(mediaType) || MediaType.APPLICATION_XML_TYPE.isCompatible(mediaType) + || MediaType.TEXT_XML_TYPE.isCompatible(mediaType); + } + + protected Object convert(InputStream content, String contentType, Class<?> type) throws IOException { + if (type == DataSource.class) { + return type.cast(new InputStreamDataSource(content, contentType)); + } else if (type == InputStream.class) { + return type.cast(content); + } else if (type == Reader.class) { + return type.cast(new InputStreamReader(content, "UTF-8")); + } else if (type == String.class) { + try { + StringWriter sw = new StringWriter(); + InputStreamReader reader = new InputStreamReader(content, "UTF-8"); + char[] buf = new char[8192]; + while (true) { + int size = reader.read(buf); + if (size < 0) { + break; + } + sw.write(buf, 0, size); + } + return type.cast(sw.toString()); + } catch (Exception e) { + throw new IllegalArgumentException(e); + } + } else if (type == byte[].class) { + try { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + byte[] buf = new byte[8192]; + while (true) { + int size = content.read(buf); + if (size < 0) { + break; + } + bos.write(buf, 0, size); + } + return type.cast(bos.toByteArray()); + } catch (Exception e) { + throw new IllegalArgumentException(e); + } + } else { + return content; + } + } + + protected void write(OutputStream out, Object content, Class<?> type) throws IOException { + if (content == null) { + return; + } + InputStream in = null; + if (DataSource.class.isAssignableFrom(type)) { + in = ((DataSource)content).getInputStream(); + } else if (InputStream.class.isAssignableFrom(type)) { + in = (InputStream)content; + } else if (type == String.class) { + in = new ByteArrayInputStream(((String)content).getBytes("UTF-8")); + } else if (type == byte[].class) { + in = new ByteArrayInputStream((byte[])content); + } + if (in == null) { + throw new IllegalArgumentException("Type is not supported: " + type); + } + byte[] buf = new byte[8192]; + while (true) { + int len = in.read(buf); + if (len < 0) { + in.close(); + break; + } + out.write(buf, 0, len); + } + } + + public static final class InputStreamDataSource implements DataSource { + + public static final String DEFAULT_TYPE = "application/octet-stream"; + + private final InputStream in; + private final String ctype; + + public InputStreamDataSource(InputStream in) { + this(in, null); + } + + public InputStreamDataSource(InputStream in, String ctype) { + this.in = in; + this.ctype = (ctype != null) ? ctype : DEFAULT_TYPE; + } + + public String getContentType() { + return ctype; + } + + public String getName() { + return null; + } + + public InputStream getInputStream() throws IOException { + return in; + } + + public OutputStream getOutputStream() throws IOException { + return null; + } + + } +} diff --git a/sca-java-2.x/tags/2.0-M5.1/modules/binding-rest-runtime/src/main/java/org/apache/tuscany/sca/binding/rest/provider/DataBindingJAXRSReader.java b/sca-java-2.x/tags/2.0-M5.1/modules/binding-rest-runtime/src/main/java/org/apache/tuscany/sca/binding/rest/provider/DataBindingJAXRSReader.java new file mode 100644 index 0000000000..d6df778feb --- /dev/null +++ b/sca-java-2.x/tags/2.0-M5.1/modules/binding-rest-runtime/src/main/java/org/apache/tuscany/sca/binding/rest/provider/DataBindingJAXRSReader.java @@ -0,0 +1,85 @@ +/* + * 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 org.apache.tuscany.sca.binding.rest.provider; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.Collections; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyReader; +import javax.ws.rs.ext.Provider; + +import org.apache.tuscany.sca.core.ExtensionPointRegistry; +import org.apache.tuscany.sca.interfacedef.DataType; +import org.apache.tuscany.sca.interfacedef.impl.DataTypeImpl; + +/** + * The generic JAX-RS message body reader based on Tuscany's databindingframework + */ +@Provider +public class DataBindingJAXRSReader<T> extends DataBindingJAXRSProvider implements MessageBodyReader<T> { + + public DataBindingJAXRSReader(ExtensionPointRegistry registry) { + super(registry); + } + + public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { +// DataType dataType = createDataType(type, genericType); + return supports(type, genericType, annotations, mediaType); + } + + public T readFrom(Class<T> type, + Type genericType, + Annotation[] annotations, + MediaType mediaType, + MultivaluedMap<String, String> httpHeaders, + InputStream entityStream) throws IOException, WebApplicationException { + + Object source = entityStream; + DataType targetDataType = createDataType(type, genericType); + + String dataBinding = null; + + mediaType = new MediaType(mediaType.getType(), mediaType.getSubtype()); + // FIXME: [rfeng] This is a hack to handle application/json + if (MediaType.APPLICATION_JSON_TYPE.equals(mediaType)) { + dataBinding = mediaType.toString() + "#" + InputStream.class.getName(); + } else if (MediaType.APPLICATION_XML_TYPE.equals(mediaType) || MediaType.TEXT_XML_TYPE.equals(mediaType)) { + dataBinding = InputStream.class.getName(); + } else { + dataBinding = targetDataType.getDataBinding(); + source = convert(entityStream, mediaType.toString(), type); + return (T) source; + } + DataType sourceDataType = + new DataTypeImpl(dataBinding, InputStream.class, InputStream.class, InputStream.class); + + Object result = mediator.mediate(source, sourceDataType, targetDataType, Collections.<String, Object>emptyMap()); + return (T)result; + } + + + +} diff --git a/sca-java-2.x/tags/2.0-M5.1/modules/binding-rest-runtime/src/main/java/org/apache/tuscany/sca/binding/rest/provider/DataBindingJAXRSWriter.java b/sca-java-2.x/tags/2.0-M5.1/modules/binding-rest-runtime/src/main/java/org/apache/tuscany/sca/binding/rest/provider/DataBindingJAXRSWriter.java new file mode 100644 index 0000000000..740b685b22 --- /dev/null +++ b/sca-java-2.x/tags/2.0-M5.1/modules/binding-rest-runtime/src/main/java/org/apache/tuscany/sca/binding/rest/provider/DataBindingJAXRSWriter.java @@ -0,0 +1,86 @@ +/* + * 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 org.apache.tuscany.sca.binding.rest.provider; + +import java.io.IOException; +import java.io.OutputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.Collections; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyWriter; +import javax.ws.rs.ext.Provider; + +import org.apache.tuscany.sca.core.ExtensionPointRegistry; +import org.apache.tuscany.sca.interfacedef.DataType; +import org.apache.tuscany.sca.interfacedef.impl.DataTypeImpl; + +/** + * The generic JAX-RS message body writer based on Tuscany's databindingframework + */ +@Provider +public class DataBindingJAXRSWriter<T> extends DataBindingJAXRSProvider implements MessageBodyWriter<T> { + + public DataBindingJAXRSWriter(ExtensionPointRegistry registry) { + super(registry); + } + + public long getSize(T t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return -1; + } + + public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { + // DataType dataType = createDataType(type, genericType); + return supports(type, genericType, annotations, mediaType); + } + + public void writeTo(T t, + Class<?> type, + Type genericType, + Annotation[] annotations, + MediaType mediaType, + MultivaluedMap<String, Object> httpHeaders, + OutputStream entityStream) throws IOException, WebApplicationException { + DataType dataType = createDataType(type, genericType); + mediaType = new MediaType(mediaType.getType(), mediaType.getSubtype()); + String dataBinding = OutputStream.class.getName(); + // FIXME: [rfeng] This is a hack to handle application/json + if (MediaType.APPLICATION_JSON_TYPE.equals(mediaType)) { + dataBinding = mediaType.toString() + "#" + OutputStream.class.getName(); + } else if (MediaType.APPLICATION_XML_TYPE.equals(mediaType) || MediaType.TEXT_XML_TYPE.equals(mediaType)) { + dataBinding = OutputStream.class.getName(); + } else { + dataBinding = dataType.getDataBinding(); + write(entityStream, t, type); + return; + } + DataType targetDataType = + new DataTypeImpl(dataBinding, OutputStream.class, OutputStream.class, OutputStream.class); + // dataBindingExtensionPoint.introspectType(targetDataType, null); + + introspectAnnotations(annotations, targetDataType); + + mediator.mediate(t, entityStream, dataType, targetDataType, Collections.<String, Object> emptyMap()); + } + +} diff --git a/sca-java-2.x/tags/2.0-M5.1/modules/binding-rest-runtime/src/main/java/org/apache/tuscany/sca/binding/rest/provider/RESTBindingInvoker.java b/sca-java-2.x/tags/2.0-M5.1/modules/binding-rest-runtime/src/main/java/org/apache/tuscany/sca/binding/rest/provider/RESTBindingInvoker.java new file mode 100644 index 0000000000..c8089904ae --- /dev/null +++ b/sca-java-2.x/tags/2.0-M5.1/modules/binding-rest-runtime/src/main/java/org/apache/tuscany/sca/binding/rest/provider/RESTBindingInvoker.java @@ -0,0 +1,285 @@ +/* + * 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 org.apache.tuscany.sca.binding.rest.provider; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.net.URI; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import javax.ws.rs.Consumes; +import javax.ws.rs.CookieParam; +import javax.ws.rs.DELETE; +import javax.ws.rs.FormParam; +import javax.ws.rs.GET; +import javax.ws.rs.HEAD; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.HttpMethod; +import javax.ws.rs.MatrixParam; +import javax.ws.rs.OPTIONS; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Application; +import javax.ws.rs.core.Cookie; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.UriBuilder; + +import org.apache.tuscany.sca.assembly.WireFormat; +import org.apache.tuscany.sca.binding.rest.RESTBinding; +import org.apache.tuscany.sca.binding.rest.wireformat.json.JSONWireFormat; +import org.apache.tuscany.sca.binding.rest.wireformat.xml.XMLWireFormat; +import org.apache.tuscany.sca.common.http.HTTPCacheContext; +import org.apache.tuscany.sca.common.http.HTTPHeader; +import org.apache.tuscany.sca.core.ExtensionPointRegistry; +import org.apache.tuscany.sca.interfacedef.Operation; +import org.apache.tuscany.sca.interfacedef.java.JavaOperation; +import org.apache.tuscany.sca.invocation.Invoker; +import org.apache.tuscany.sca.invocation.Message; +import org.apache.wink.client.ClientConfig; +import org.apache.wink.client.Resource; +import org.apache.wink.client.RestClient; +import org.apache.wink.client.handlers.BasicAuthSecurityHandler; + +/** + * + */ +public class RESTBindingInvoker implements Invoker { + private ExtensionPointRegistry registry; + private RESTBinding binding; + private Operation operation; + private RestClient restClient; + private String httpMethod; + private Class<?> responseType; + + public RESTBindingInvoker(ExtensionPointRegistry registry, RESTBinding binding, Operation operation) { + super(); + this.registry = registry; + this.binding = binding; + this.operation = operation; + this.restClient = createRestClient(); + } + + private static Map<Class<?>, String> mapping = new HashMap<Class<?>, String>(); + static { + mapping.put(GET.class, HttpMethod.GET); + mapping.put(POST.class, HttpMethod.POST); + mapping.put(PUT.class, HttpMethod.PUT); + mapping.put(DELETE.class, HttpMethod.DELETE); + mapping.put(HEAD.class, HttpMethod.HEAD); + mapping.put(OPTIONS.class, HttpMethod.OPTIONS); + } + + private static <T extends Annotation> T getAnnotation(Annotation[] annotations, Class<T> type) { + for (Annotation a : annotations) { + if (a.annotationType() == type) { + return type.cast(a); + } + } + return null; + } + + private RestClient createRestClient() { + ClientConfig config = new ClientConfig(); + + // configureBasicAuth(config, userName, password); + + config.applications(new Application() { + + @Override + public Set<Class<?>> getClasses() { + return Collections.emptySet(); + } + + @Override + public Set<Object> getSingletons() { + Set<Object> providers = new HashSet<Object>(); + providers.add(new DataBindingJAXRSReader(registry)); + providers.add(new DataBindingJAXRSWriter(registry)); + return providers; + } + + }); + RestClient client = new RestClient(config); + + for (Map.Entry<Class<?>, String> e : mapping.entrySet()) { + if (operation.getAttributes().get(e.getKey()) != null) { + httpMethod = e.getValue(); + break; + } + } + + if (operation.getOutputType() != null) { + responseType = operation.getOutputType().getPhysical(); + } else { + responseType = null; + } + return client; + } + + private void configureBasicAuth(ClientConfig config, String userName, String password) { + BasicAuthSecurityHandler basicAuthSecurityHandler = new BasicAuthSecurityHandler(); + basicAuthSecurityHandler.setUserName(userName); + basicAuthSecurityHandler.setPassword(password); + config.handlers(basicAuthSecurityHandler); + } + + public Message invoke(Message msg) { + + Object entity = null; + Object[] args = msg.getBody(); + + URI uri = URI.create(binding.getURI()); + UriBuilder uriBuilder = UriBuilder.fromUri(uri); + + Method method = ((JavaOperation)operation).getJavaMethod(); + uriBuilder.path(method); + + Map<String, Object> pathParams = new HashMap<String, Object>(); + Map<String, Object> matrixParams = new HashMap<String, Object>(); + Map<String, Object> queryParams = new HashMap<String, Object>(); + Map<String, Object> headerParams = new HashMap<String, Object>(); + Map<String, Object> formParams = new HashMap<String, Object>(); + Map<String, Object> cookieParams = new HashMap<String, Object>(); + + boolean isEntity = true; + for (int i = 0; i < method.getParameterTypes().length; i++) { + Annotation[] annotations = method.getParameterAnnotations()[i]; + PathParam pathParam = getAnnotation(annotations, PathParam.class); + if (pathParam != null) { + isEntity = false; + pathParams.put(pathParam.value(), args[i]); + } + MatrixParam matrixParam = getAnnotation(annotations, MatrixParam.class); + if (matrixParam != null) { + isEntity = false; + matrixParams.put(matrixParam.value(), args[i]); + } + QueryParam queryParam = getAnnotation(annotations, QueryParam.class); + if (queryParam != null) { + isEntity = false; + queryParams.put(queryParam.value(), args[i]); + } + HeaderParam headerParam = getAnnotation(annotations, HeaderParam.class); + if (headerParam != null) { + isEntity = false; + headerParams.put(headerParam.value(), args[i]); + } + FormParam formParam = getAnnotation(annotations, FormParam.class); + if (formParam != null) { + isEntity = false; + formParams.put(formParam.value(), args[i]); + } + CookieParam cookieParam = getAnnotation(annotations, CookieParam.class); + if (cookieParam != null) { + isEntity = false; + cookieParams.put(cookieParam.value(), args[i]); + } + if(isEntity) { + entity = args[i]; + } + } + + for (Map.Entry<String, Object> p : queryParams.entrySet()) { + uriBuilder.replaceQueryParam(p.getKey(), p.getValue()); + } + for (Map.Entry<String, Object> p : matrixParams.entrySet()) { + uriBuilder.replaceMatrixParam(p.getKey(), p.getValue()); + } + + uri = uriBuilder.buildFromMap(pathParams); + Resource resource = restClient.resource(uri); + + for (Map.Entry<String, Object> p : headerParams.entrySet()) { + resource.header(p.getKey(), String.valueOf(p.getValue())); + } + + for (Map.Entry<String, Object> p : cookieParams.entrySet()) { + Cookie cookie = new Cookie(p.getKey(), String.valueOf(p.getValue())); + resource.cookie(cookie); + } + + resource.contentType(getContentType()); + resource.accept(getAccepts()); + + //handles declarative headers configured on the composite + for(HTTPHeader header : binding.getHttpHeaders()) { + //treat special headers that need to be calculated + if(header.getName().equalsIgnoreCase("Expires")) { + GregorianCalendar calendar = new GregorianCalendar(); + calendar.setTime(new Date()); + + calendar.add(Calendar.HOUR, Integer.parseInt(header.getValue())); + + resource.header("Expires", HTTPCacheContext.RFC822DateFormat.format( calendar.getTime() )); + } else { + //default behaviour to pass the header value to HTTP response + resource.header(header.getName(), header.getValue()); + } + } + + Object result = resource.invoke(httpMethod, responseType, entity); + msg.setBody(result); + return msg; + } + + private String getContentType() { + String contentType = MediaType.APPLICATION_OCTET_STREAM; + Consumes consumes = ((JavaOperation)operation).getJavaMethod().getAnnotation(Consumes.class); + if (consumes != null && consumes.value().length > 0) { + contentType = consumes.value()[0]; + } + WireFormat wf = binding.getRequestWireFormat(); + if (wf != null) { + if (XMLWireFormat.REST_WIREFORMAT_XML_QNAME.equals(wf.getSchemaName())) { + contentType = MediaType.APPLICATION_XML; + } else if (JSONWireFormat.REST_WIREFORMAT_JSON_QNAME.equals(wf.getSchemaName())) { + contentType = MediaType.APPLICATION_JSON; + } + } + return contentType; + } + + private String[] getAccepts() { + String accepts[] = {MediaType.APPLICATION_OCTET_STREAM}; + Produces produces = ((JavaOperation)operation).getJavaMethod().getAnnotation(Produces.class); + if (produces != null) { + accepts = produces.value(); + } + WireFormat wf = binding.getResponseWireFormat(); + if (wf != null) { + if (XMLWireFormat.REST_WIREFORMAT_XML_QNAME.equals(wf.getSchemaName())) { + accepts = new String[] {MediaType.APPLICATION_XML}; + } else if (JSONWireFormat.REST_WIREFORMAT_JSON_QNAME.equals(wf.getSchemaName())) { + accepts = new String[] {MediaType.APPLICATION_JSON}; + } + } + return accepts; + } +} diff --git a/sca-java-2.x/tags/2.0-M5.1/modules/binding-rest-runtime/src/main/java/org/apache/tuscany/sca/binding/rest/provider/RESTBindingListenerServlet.java b/sca-java-2.x/tags/2.0-M5.1/modules/binding-rest-runtime/src/main/java/org/apache/tuscany/sca/binding/rest/provider/RESTBindingListenerServlet.java new file mode 100644 index 0000000000..4583b11879 --- /dev/null +++ b/sca-java-2.x/tags/2.0-M5.1/modules/binding-rest-runtime/src/main/java/org/apache/tuscany/sca/binding/rest/provider/RESTBindingListenerServlet.java @@ -0,0 +1,582 @@ +/* + * 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 org.apache.tuscany.sca.binding.rest.provider; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URLDecoder; +import java.text.ParseException; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.tuscany.sca.assembly.Binding; +import org.apache.tuscany.sca.binding.rest.RESTBinding; +import org.apache.tuscany.sca.common.http.HTTPCacheContext; +import org.apache.tuscany.sca.common.http.HTTPContentTypeMapper; +import org.apache.tuscany.sca.common.http.HTTPContext; +import org.apache.tuscany.sca.common.http.HTTPHeader; +import org.apache.tuscany.sca.invocation.Invoker; +import org.apache.tuscany.sca.invocation.Message; +import org.apache.tuscany.sca.invocation.MessageFactory; + +/** + * Servlet responsible for dispatching HTTP requests to the + * target component implementation. + * + * @version $Rev$ $Date$ + */ +public class RESTBindingListenerServlet extends HttpServlet { + private static final long serialVersionUID = 2865466417329430610L; + + transient private MessageFactory messageFactory; + + transient private RESTBinding binding; + transient private Invoker bindingInvoker; + + private Invoker invoker; + private Invoker getInvoker; + private Invoker conditionalGetInvoker; + private Invoker putInvoker; + private Invoker conditionalPutInvoker; + private Invoker postInvoker; + private Invoker conditionalPostInvoker; + private Invoker deleteInvoker; + private Invoker conditionalDeleteInvoker; + + /** + * Constructs a new RESTServiceListenerServlet. + */ + public RESTBindingListenerServlet(Binding binding, Invoker bindingInvoker, MessageFactory messageFactory) { + this.binding = (RESTBinding) binding; + this.bindingInvoker = bindingInvoker; + this.messageFactory = messageFactory; + } + + @Override + protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if( binding.getOperationSelector() != null || binding.getRequestWireFormat() != null) { + HTTPContext bindingContext = new HTTPContext(); + bindingContext.setHttpRequest(request); + bindingContext.setHttpResponse(response); + + // Dispatch the service interaction to the service invoker + Message requestMessage = messageFactory.createMessage(); + requestMessage.setBindingContext(bindingContext); + + requestMessage.setBody(new Object[] {request.getInputStream()}); + + Message responseMessage = bindingInvoker.invoke(requestMessage); + + // return response to client + if (responseMessage.isFault()) { + // Turn a fault into an exception + Throwable e = (Throwable)responseMessage.getBody(); + response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.toString()); + } else { + + for(HTTPHeader header : binding.getHttpHeaders()) { + //treat special headers that need to be calculated + if(header.getName().equalsIgnoreCase("Expires")) { + GregorianCalendar calendar = new GregorianCalendar(); + calendar.setTime(new Date()); + + calendar.add(Calendar.HOUR, Integer.parseInt(header.getValue())); + + response.setHeader("Expires", HTTPCacheContext.RFC822DateFormat.format( calendar.getTime() )); + } else { + //default behaviour to pass the header value to HTTP response + response.setHeader(header.getName(), header.getValue()); + } + + } + + //handle void operations + write(response.getOutputStream(), responseMessage.getBody()); + response.getOutputStream().flush(); + response.getOutputStream().close(); + } + } else { + super.service(request, response); + } + + + } + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + // Get the request path + String path = URLDecoder.decode(request.getRequestURI().substring(request.getServletPath().length()), "UTF-8"); + if (path.length() ==0) { + // Redirect to a URL ending with / to make relative hrefs work + // relative to the served resource. + response.sendRedirect(request.getRequestURL().append('/').toString()); + return; + } + + // Invoke the get operation on the service implementation + Message requestMessage = messageFactory.createMessage(); + + String id = path.substring(1); + + Message responseMessage = null; + HTTPCacheContext cacheContext = null; + try { + cacheContext = HTTPCacheContext.createCacheContextFromRequest(request); + } catch (ParseException e) { + + } + + if (path == null || path.length() == 0 || path.equals("/")) { + + } + + // Route message based on availability of cache info and cache methods + if (( cacheContext != null ) && (cacheContext.isEnabled()) && (conditionalGetInvoker != null )) { + if(id != null && id.length() > 0) { + requestMessage.setBody(new Object[] {id, cacheContext}); + } else { + requestMessage.setBody(new Object[] {cacheContext}); + } + + responseMessage = conditionalGetInvoker.invoke(requestMessage); + } else { + if(id != null && id.length() > 0) { + requestMessage.setBody(new Object[] {id}); + } else { + //requestMessage.setBody(new Object[] {id}); + } + + responseMessage = getInvoker.invoke(requestMessage); + } + + if (responseMessage.isFault()) { + Object body = responseMessage.getBody(); + + int index = -1; + if ( -1 < (index = body.getClass().getName().indexOf( "NotModifiedException")) ) { + if ( index > -1 ) { + response.sendError( HttpServletResponse.SC_NOT_MODIFIED, body.toString().substring( index )); + } else { + response.sendError( HttpServletResponse.SC_NOT_MODIFIED ); + } + return; + } else if ( -1 < (index = body.getClass().getName().indexOf( "PreconditionFailedException")) ) { + if ( index > -1 ) { + response.sendError( HttpServletResponse.SC_PRECONDITION_FAILED, body.toString().substring( index )); + } else { + response.sendError( HttpServletResponse.SC_PRECONDITION_FAILED ); + } + return; + } + + throw new ServletException((Throwable)responseMessage.getBody()); + } + + if(response.getContentType() == null || response.getContentType().length() == 0){ + // Calculate content-type based on extension + String contentType = HTTPContentTypeMapper.getContentType(id); + if(contentType != null && contentType.length() >0) { + response.setContentType(contentType); + } + } + + // Write the response from the service implementation to the response + // output stream + InputStream is = (InputStream)responseMessage.getBody(); + OutputStream os = response.getOutputStream(); + byte[] buffer = new byte[2048]; + for (;;) { + int n = is.read(buffer); + if (n <= 0) + break; + os.write(buffer, 0, n); + } + os.flush(); + os.close(); + } + + @Override + protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + // Get the request path + String path = URLDecoder.decode(request.getRequestURI().substring(request.getServletPath().length()), "UTF-8"); + if (path.length() ==0) { + // Redirect to a URL ending with / to make relative hrefs work + // relative to the served resource. + response.sendRedirect(request.getRequestURL().append('/').toString()); + return; + } + + // Invoke the get operation on the service implementation + Message requestMessage = messageFactory.createMessage(); + String id = path.substring(1); + + Message responseMessage = null; + HTTPCacheContext cacheContext = null; + try { + cacheContext = HTTPCacheContext.createCacheContextFromRequest(request); + } catch (ParseException e) { + } + + // Route message based on availability of cache info and cache methods + if (( cacheContext != null ) && (cacheContext.isEnabled()) && (conditionalDeleteInvoker != null )) { + requestMessage.setBody(new Object[] {id, cacheContext}); + responseMessage = conditionalDeleteInvoker.invoke(requestMessage); + } else { + requestMessage.setBody(new Object[] {id}); + responseMessage = deleteInvoker.invoke(requestMessage); + } + if (responseMessage.isFault()) { + Object body = responseMessage.getBody(); + + int index = -1; + if ( -1 < (index = body.getClass().getName().indexOf( "NotModifiedException")) ) { + if ( index > -1 ) { + response.sendError( HttpServletResponse.SC_NOT_MODIFIED, body.toString().substring( index )); + } else { + response.sendError( HttpServletResponse.SC_NOT_MODIFIED ); + } + return; + } else if ( -1 < (index = body.getClass().getName().indexOf( "PreconditionFailedException")) ) { + if ( index > -1 ) { + response.sendError( HttpServletResponse.SC_PRECONDITION_FAILED, body.toString().substring( index )); + } else { + response.sendError( HttpServletResponse.SC_PRECONDITION_FAILED ); + } + return; + } + + throw new ServletException((Throwable)responseMessage.getBody()); + } + + // Write the response from the service implementation to the response + // output stream + InputStream is = (InputStream)responseMessage.getBody(); + OutputStream os = response.getOutputStream(); + byte[] buffer = new byte[2048]; + for (;;) { + int n = is.read(buffer); + if (n <= 0) + break; + os.write(buffer, 0, n); + } + os.flush(); + os.close(); + } + + @Override + protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + // Get the request path + String path = URLDecoder.decode(request.getRequestURI().substring(request.getServletPath().length()), "UTF-8"); + if (path.length() ==0) { + // Redirect to a URL ending with / to make relative hrefs work + // relative to the served resource. + response.sendRedirect(request.getRequestURL().append('/').toString()); + return; + } + + // Invoke the get operation on the service implementation + Message requestMessage = messageFactory.createMessage(); + String id = path.substring(1); + + Message responseMessage = null; + HTTPCacheContext cacheContext = null; + try { + cacheContext = HTTPCacheContext.createCacheContextFromRequest(request); + } catch (ParseException e) { + } + + // Route message based on availability of cache info and cache methods + if (( cacheContext != null ) && (cacheContext.isEnabled()) && (conditionalPutInvoker != null )) { + requestMessage.setBody(new Object[] {id, cacheContext}); + responseMessage = conditionalPutInvoker.invoke(requestMessage); + } else { + requestMessage.setBody(new Object[] {id}); + responseMessage = putInvoker.invoke(requestMessage); + } + if (responseMessage.isFault()) { + Object body = responseMessage.getBody(); + + int index = -1; + if ( -1 < (index = body.getClass().getName().indexOf( "NotModifiedException")) ) { + if ( index > -1 ) { + response.sendError( HttpServletResponse.SC_NOT_MODIFIED, body.toString().substring( index )); + } else { + response.sendError( HttpServletResponse.SC_NOT_MODIFIED ); + } + return; + } else if ( -1 < (index = body.getClass().getName().indexOf( "PreconditionFailedException")) ) { + if ( index > -1 ) { + response.sendError( HttpServletResponse.SC_PRECONDITION_FAILED, body.toString().substring( index )); + } else { + response.sendError( HttpServletResponse.SC_PRECONDITION_FAILED ); + } + return; + } + + throw new ServletException((Throwable)responseMessage.getBody()); + } + + // Write the response from the service implementation to the response + // output stream + InputStream is = (InputStream)responseMessage.getBody(); + OutputStream os = response.getOutputStream(); + byte[] buffer = new byte[2048]; + for (;;) { + int n = is.read(buffer); + if (n <= 0) + break; + os.write(buffer, 0, n); + } + os.flush(); + os.close(); + } + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + // Get the request path + String path = URLDecoder.decode(request.getRequestURI().substring(request.getServletPath().length()), "UTF-8"); + if (path.length() ==0) { + // Redirect to a URL ending with / to make relative hrefs work + // relative to the served resource. + response.sendRedirect(request.getRequestURL().append('/').toString()); + return; + } + + // Invoke the get operation on the service implementation + Message requestMessage = messageFactory.createMessage(); + // String id = path.substring(1); + + Message responseMessage = null; + HTTPCacheContext cacheContext = null; + try { + cacheContext = HTTPCacheContext.createCacheContextFromRequest(request); + } catch (ParseException e) { + } + + // Route message based on availability of cache info and cache methods + if (( cacheContext != null ) && (cacheContext.isEnabled()) && (conditionalPostInvoker != null )) { + requestMessage.setBody(new Object[] {cacheContext}); + responseMessage = conditionalPostInvoker.invoke(requestMessage); + } else { + requestMessage.setBody(new Object[] {}); + responseMessage = postInvoker.invoke(requestMessage); + } + if (responseMessage.isFault()) { + Object body = responseMessage.getBody(); + + int index = -1; + if ( -1 < (index = body.getClass().getName().indexOf( "NotModifiedException")) ) { + if ( index > -1 ) + response.sendError( HttpServletResponse.SC_NOT_MODIFIED, body.toString().substring( index )); + else + response.sendError( HttpServletResponse.SC_NOT_MODIFIED ); + return; + } else if ( -1 < (index = body.getClass().getName().indexOf( "PreconditionFailedException")) ) { + if ( index > -1 ) + response.sendError( HttpServletResponse.SC_PRECONDITION_FAILED, body.toString().substring( index )); + else + response.sendError( HttpServletResponse.SC_PRECONDITION_FAILED ); + return; + } + + throw new ServletException((Throwable)responseMessage.getBody()); + } + + + // Test if the ETag and LastModified are returned as a cache context. + Object body = responseMessage.getBody(); + if ( body.getClass() == HTTPCacheContext.class ) { + // Transfer to header if so. + HTTPCacheContext cc = (HTTPCacheContext)responseMessage.getBody(); + if (( cc != null ) && ( cc.isEnabled() )) { + String eTag = cc.getETag(); + if ( eTag != null ) { + response.setHeader( "ETag", cc.getETag() ); + } + String lastModified = cc.getLastModified(); + if ( lastModified != null) { + response.setHeader( "LastModified", cc.getLastModified() ); + } + } + } + } + + + public void setInvoker(Invoker invoker) { + this.invoker = invoker; + } + + public Invoker getInvoker() { + return invoker; + } + + + /** + * @return the getInvoker + */ + public Invoker getGetInvoker() { + return getInvoker; + } + + /** + * @param getInvoker the getInvoker to set + */ + public void setGetInvoker(Invoker getInvoker) { + this.getInvoker = getInvoker; + } + + /** + * @return the conditionalGetInvoker + */ + public Invoker getConditionalGetInvoker() { + return conditionalGetInvoker; + } + + /** + * @param conditionalGetInvoker the conditionalGetInvoker to set + */ + public void setConditionalGetInvoker(Invoker conditionalGetInvoker) { + this.conditionalGetInvoker = conditionalGetInvoker; + } + + /** + * @return the putInvoker + */ + public Invoker getPutInvoker() { + return putInvoker; + } + + /** + * @param putInvoker the putInvoker to set + */ + public void setPutInvoker(Invoker putInvoker) { + this.putInvoker = putInvoker; + } + + /** + * @return the conditionalPutInvoker + */ + public Invoker getConditionalPutInvoker() { + return conditionalPutInvoker; + } + + /** + * @param conditionalPutInvoker the conditionalPutInvoker to set + */ + public void setConditionalPutInvoker(Invoker conditionalPutInvoker) { + this.conditionalPutInvoker = conditionalPutInvoker; + } + + /** + * @return the postInvoker + */ + public Invoker getPostInvoker() { + return postInvoker; + } + + /** + * @param postInvoker the postInvoker to set + */ + public void setPostInvoker(Invoker postInvoker) { + this.postInvoker = postInvoker; + } + + /** + * @return the conditionalPostInvoker + */ + public Invoker getConditionalPostInvoker() { + return conditionalPostInvoker; + } + + /** + * @param conditionalPostInvoker the conditionalPostInvoker to set + */ + public void setConditionalPostInvoker(Invoker conditionalPostInvoker) { + this.conditionalPostInvoker = conditionalPostInvoker; + } + + /** + * @return the deleteInvoker + */ + public Invoker getDeleteInvoker() { + return deleteInvoker; + } + + /** + * @param deleteInvoker the deleteInvoker to set + */ + public void setDeleteInvoker(Invoker deleteInvoker) { + this.deleteInvoker = deleteInvoker; + } + + /** + * @return the conditionalDeleteInvoker + */ + public Invoker getConditionalDeleteInvoker() { + return conditionalDeleteInvoker; + } + + /** + * @param conditionalDeleteInvoker the conditionalDeleteInvoker to set + */ + public void setConditionalDeleteInvoker(Invoker conditionalDeleteInvoker) { + this.conditionalDeleteInvoker = conditionalDeleteInvoker; + } + + /** + * + * Utility methods + * + */ + + + + private void write(OutputStream out, Object obj) throws IOException { + if (obj == null) { + return; + } + if (obj instanceof String) { + out.write(((String)obj).getBytes("UTF-8")); + } else if (obj instanceof byte[]) { + out.write((byte[])obj); + } else if (obj instanceof InputStream) { + byte[] buf = new byte[8192]; + InputStream in = (InputStream)obj; + while (true) { + int size = in.read(buf); + if (size < 0) { + break; + } + out.write(buf, 0, size); + } + } else { + out.write(obj.toString().getBytes("UTF-8")); + } + } + + +} diff --git a/sca-java-2.x/tags/2.0-M5.1/modules/binding-rest-runtime/src/main/java/org/apache/tuscany/sca/binding/rest/provider/RESTBindingProviderFactory.java b/sca-java-2.x/tags/2.0-M5.1/modules/binding-rest-runtime/src/main/java/org/apache/tuscany/sca/binding/rest/provider/RESTBindingProviderFactory.java new file mode 100644 index 0000000000..7c9ec85a9e --- /dev/null +++ b/sca-java-2.x/tags/2.0-M5.1/modules/binding-rest-runtime/src/main/java/org/apache/tuscany/sca/binding/rest/provider/RESTBindingProviderFactory.java @@ -0,0 +1,65 @@ +/* + * 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 org.apache.tuscany.sca.binding.rest.provider; + +import org.apache.tuscany.sca.binding.rest.RESTBinding; +import org.apache.tuscany.sca.core.ExtensionPointRegistry; +import org.apache.tuscany.sca.core.FactoryExtensionPoint; +import org.apache.tuscany.sca.host.http.ServletHost; +import org.apache.tuscany.sca.host.http.ServletHostHelper; +import org.apache.tuscany.sca.invocation.MessageFactory; +import org.apache.tuscany.sca.provider.BindingProviderFactory; +import org.apache.tuscany.sca.provider.ReferenceBindingProvider; +import org.apache.tuscany.sca.provider.ServiceBindingProvider; +import org.apache.tuscany.sca.runtime.RuntimeEndpoint; +import org.apache.tuscany.sca.runtime.RuntimeEndpointReference; + + +/** + * Factory for REST binding providers. + * + * @version $Rev$ $Date$ + */ +public class RESTBindingProviderFactory implements BindingProviderFactory<RESTBinding> { + private ExtensionPointRegistry extensionPoints; + private MessageFactory messageFactory; + private ServletHost servletHost; + + public RESTBindingProviderFactory(ExtensionPointRegistry extensionPoints) { + this.extensionPoints = extensionPoints; + this.servletHost = ServletHostHelper.getServletHost(extensionPoints); + FactoryExtensionPoint modelFactories = extensionPoints.getExtensionPoint(FactoryExtensionPoint.class); + messageFactory = modelFactories.getFactory(MessageFactory.class); + } + + public ReferenceBindingProvider createReferenceBindingProvider(RuntimeEndpointReference endpointReference) { + return new RESTReferenceBindingProvider(extensionPoints, endpointReference); + // Binding REST is currently NOT supporting References + // throw new UnsupportedOperationException("binding.rest for SCA references is not implemented yet."); + } + + public ServiceBindingProvider createServiceBindingProvider(RuntimeEndpoint endpoint) { + return new RESTServiceBindingProvider(endpoint, extensionPoints, messageFactory, servletHost); + } + + public Class<RESTBinding> getModelType() { + return RESTBinding.class; + } +} diff --git a/sca-java-2.x/tags/2.0-M5.1/modules/binding-rest-runtime/src/main/java/org/apache/tuscany/sca/binding/rest/provider/RESTReferenceBindingProvider.java b/sca-java-2.x/tags/2.0-M5.1/modules/binding-rest-runtime/src/main/java/org/apache/tuscany/sca/binding/rest/provider/RESTReferenceBindingProvider.java new file mode 100644 index 0000000000..e218f32573 --- /dev/null +++ b/sca-java-2.x/tags/2.0-M5.1/modules/binding-rest-runtime/src/main/java/org/apache/tuscany/sca/binding/rest/provider/RESTReferenceBindingProvider.java @@ -0,0 +1,64 @@ +/* + * 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 org.apache.tuscany.sca.binding.rest.provider; + +import org.apache.tuscany.sca.binding.rest.RESTBinding; +import org.apache.tuscany.sca.core.ExtensionPointRegistry; +import org.apache.tuscany.sca.interfacedef.InterfaceContract; +import org.apache.tuscany.sca.interfacedef.Operation; +import org.apache.tuscany.sca.invocation.Invoker; +import org.apache.tuscany.sca.provider.EndpointReferenceProvider; +import org.apache.tuscany.sca.runtime.RuntimeEndpointReference; + +/** + * + */ +public class RESTReferenceBindingProvider implements EndpointReferenceProvider { + private ExtensionPointRegistry registry; + private RuntimeEndpointReference endpointReference; + + public RESTReferenceBindingProvider(ExtensionPointRegistry registry, RuntimeEndpointReference endpointReference) { + super(); + this.registry = registry; + this.endpointReference = endpointReference; + } + + public void configure() { + } + + public Invoker createInvoker(Operation operation) { + return new RESTBindingInvoker(registry, (RESTBinding)endpointReference.getBinding(), operation); + } + + public InterfaceContract getBindingInterfaceContract() { + return endpointReference.getComponentReferenceInterfaceContract(); + } + + public boolean supportsOneWayInvocation() { + return false; + } + + public void start() { + } + + public void stop() { + } + +} diff --git a/sca-java-2.x/tags/2.0-M5.1/modules/binding-rest-runtime/src/main/java/org/apache/tuscany/sca/binding/rest/provider/RESTServiceBindingProvider.java b/sca-java-2.x/tags/2.0-M5.1/modules/binding-rest-runtime/src/main/java/org/apache/tuscany/sca/binding/rest/provider/RESTServiceBindingProvider.java new file mode 100644 index 0000000000..46002e3226 --- /dev/null +++ b/sca-java-2.x/tags/2.0-M5.1/modules/binding-rest-runtime/src/main/java/org/apache/tuscany/sca/binding/rest/provider/RESTServiceBindingProvider.java @@ -0,0 +1,376 @@ +/* + * 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 org.apache.tuscany.sca.binding.rest.provider; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.net.URI; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import javax.servlet.Servlet; +import javax.ws.rs.core.Application; +import javax.ws.rs.core.MediaType; +import javax.xml.namespace.QName; + +import org.apache.tuscany.sca.binding.rest.RESTBinding; +import org.apache.tuscany.sca.binding.rest.wireformat.json.JSONWireFormat; +import org.apache.tuscany.sca.binding.rest.wireformat.xml.XMLWireFormat; +import org.apache.tuscany.sca.core.ExtensionPointRegistry; +import org.apache.tuscany.sca.core.invocation.ExtensibleProxyFactory; +import org.apache.tuscany.sca.core.invocation.ProxyFactory; +import org.apache.tuscany.sca.host.http.ServletHost; +import org.apache.tuscany.sca.interfacedef.InterfaceContract; +import org.apache.tuscany.sca.interfacedef.Operation; +import org.apache.tuscany.sca.interfacedef.java.JavaInterface; +import org.apache.tuscany.sca.interfacedef.java.jaxrs.RootResourceClassGenerator; +import org.apache.tuscany.sca.invocation.Interceptor; +import org.apache.tuscany.sca.invocation.InvocationChain; +import org.apache.tuscany.sca.invocation.Invoker; +import org.apache.tuscany.sca.invocation.MessageFactory; +import org.apache.tuscany.sca.invocation.Phase; +import org.apache.tuscany.sca.provider.EndpointProvider; +import org.apache.tuscany.sca.provider.OperationSelectorProvider; +import org.apache.tuscany.sca.provider.OperationSelectorProviderFactory; +import org.apache.tuscany.sca.provider.ProviderFactoryExtensionPoint; +import org.apache.tuscany.sca.provider.WireFormatProvider; +import org.apache.tuscany.sca.provider.WireFormatProviderFactory; +import org.apache.tuscany.sca.runtime.RuntimeComponent; +import org.apache.tuscany.sca.runtime.RuntimeComponentService; +import org.apache.tuscany.sca.runtime.RuntimeEndpoint; +import org.apache.wink.server.utils.RegistrationUtils; +import org.oasisopen.sca.ServiceRuntimeException; + +/** + * Implementation of an HTTP binding provider. + * + * @version $Rev$ $Date$ + */ +public class RESTServiceBindingProvider implements EndpointProvider { + private ExtensionPointRegistry extensionPoints; + + private RuntimeEndpoint endpoint; + private RuntimeComponent component; + private RuntimeComponentService service; + private InterfaceContract serviceContract; + private RESTBinding binding; + private MessageFactory messageFactory; + + private OperationSelectorProvider osProvider; + private WireFormatProvider wfProvider; + + private ServletHost servletHost; + private String servletMapping; + private RESTBindingListenerServlet bindingListenerServlet; + + private SimpleApplication application; + + public RESTServiceBindingProvider(RuntimeEndpoint endpoint, + ExtensionPointRegistry extensionPoints, + MessageFactory messageFactory, + ServletHost servletHost) { + + this.endpoint = endpoint; + this.component = (RuntimeComponent)endpoint.getComponent(); + this.service = (RuntimeComponentService)endpoint.getService(); + this.binding = (RESTBinding)endpoint.getBinding(); + + this.extensionPoints = extensionPoints; + this.messageFactory = messageFactory; + this.servletHost = servletHost; + + // retrieve operation selector and wire format service providers + + ProviderFactoryExtensionPoint providerFactories = + extensionPoints.getExtensionPoint(ProviderFactoryExtensionPoint.class); + + if (binding.getOperationSelector() != null) { + // Configure the interceptors for operation selection + OperationSelectorProviderFactory osProviderFactory = + (OperationSelectorProviderFactory)providerFactories.getProviderFactory(binding.getOperationSelector() + .getClass()); + if (osProviderFactory != null) { + this.osProvider = osProviderFactory.createServiceOperationSelectorProvider(endpoint); + } + } + + if (binding.getRequestWireFormat() != null && binding.getResponseWireFormat() != null) { + // Configure the interceptors for wire format + WireFormatProviderFactory wfProviderFactory = + (WireFormatProviderFactory)providerFactories.getProviderFactory(binding.getRequestWireFormat() + .getClass()); + if (wfProviderFactory != null) { + this.wfProvider = wfProviderFactory.createServiceWireFormatProvider(endpoint); + } + } + + //clone the service contract to avoid databinding issues + try { + this.serviceContract = (InterfaceContract)service.getInterfaceContract().clone(); + + // configure data binding + if (this.wfProvider != null) { + wfProvider.configureWireFormatInterfaceContract(serviceContract); + } + } catch (CloneNotSupportedException e) { + this.serviceContract = service.getInterfaceContract(); + } + + } + + public void start() { + InvocationChain bindingChain = endpoint.getBindingInvocationChain(); + + application = registerWithJAXRS(); + if (application != null) { + return; + } + + // Get the invokers for the supported operations + Servlet servlet = null; + Invoker bindingInvoker = bindingChain.getHeadInvoker(); + bindingListenerServlet = new RESTBindingListenerServlet(binding, bindingInvoker, messageFactory); + for (InvocationChain invocationChain : endpoint.getInvocationChains()) { + + Operation operation = invocationChain.getTargetOperation(); + Invoker serviceInvoker = invocationChain.getHeadInvoker(); + String operationName = operation.getName(); + + if (binding.getOperationSelector() != null || binding.getRequestWireFormat() != null) { + bindingListenerServlet.setInvoker(serviceInvoker); + servlet = bindingListenerServlet; + } else if (operationName.equals("get")) { + bindingListenerServlet.setGetInvoker(serviceInvoker); + servlet = bindingListenerServlet; + } else if (operationName.equals("conditionalGet")) { + bindingListenerServlet.setConditionalGetInvoker(serviceInvoker); + servlet = bindingListenerServlet; + } else if (operationName.equals("delete")) { + bindingListenerServlet.setDeleteInvoker(serviceInvoker); + servlet = bindingListenerServlet; + } else if (operationName.equals("conditionalDelete")) { + bindingListenerServlet.setConditionalDeleteInvoker(serviceInvoker); + servlet = bindingListenerServlet; + } else if (operationName.equals("put")) { + bindingListenerServlet.setPutInvoker(serviceInvoker); + servlet = bindingListenerServlet; + } else if (operationName.equals("conditionalPut")) { + bindingListenerServlet.setConditionalPutInvoker(serviceInvoker); + servlet = bindingListenerServlet; + } else if (operationName.equals("post")) { + bindingListenerServlet.setPostInvoker(serviceInvoker); + servlet = bindingListenerServlet; + } else if (operationName.equals("conditionalPost")) { + bindingListenerServlet.setConditionalPostInvoker(serviceInvoker); + servlet = bindingListenerServlet; + } else if (operationName.equals("service")) { + servlet = new RESTServiceListenerServlet(binding, serviceInvoker, messageFactory); + break; + } + } + if (servlet == null) { + throw new IllegalStateException("No get or service method found on the service"); + } + + // Create our HTTP service listener Servlet and register it with the + // Servlet host + servletMapping = binding.getURI(); + if (!servletMapping.endsWith("/")) { + servletMapping += "/"; + } + if (!servletMapping.endsWith("*")) { + servletMapping += "*"; + } + + servletHost.addServletMapping(servletMapping, servlet); + } + + private static Map<QName, String> wireFormatToMediaTypeMapping = new HashMap<QName, String>(); + static { + wireFormatToMediaTypeMapping.put(JSONWireFormat.REST_WIREFORMAT_JSON_QNAME, MediaType.APPLICATION_JSON); + wireFormatToMediaTypeMapping.put(XMLWireFormat.REST_WIREFORMAT_XML_QNAME, MediaType.APPLICATION_XML); + } + + private SimpleApplication registerWithJAXRS() { + try { + SimpleApplication application = null; + + JavaInterface javaInterface = (JavaInterface)endpoint.getComponentServiceInterfaceContract().getInterface(); + Class<?> interfaze = javaInterface.getJavaClass(); + + boolean isJAXRS = isJAXRSResource(interfaze); + if (isJAXRS) { + application = new SimpleApplication(interfaze); + + TuscanyRESTServlet restServlet = new TuscanyRESTServlet(extensionPoints, application.resourceClass); + + // Create our HTTP service listener Servlet and register it with the + // Servlet host + servletMapping = binding.getURI(); + if (!servletMapping.endsWith("/")) { + servletMapping += "/"; + } + if (!servletMapping.endsWith("*")) { + servletMapping += "*"; + } + + servletHost.addServletMapping(servletMapping, restServlet); + RegistrationUtils.registerApplication(application, restServlet.getServletContext()); + return application; + } else { + return null; + } + } catch (Exception e) { + throw new ServiceRuntimeException(e); + } + } + + private class SimpleApplication extends Application { + private Class<?> resourceClass; + + public SimpleApplication(Class<?> resourceClass) { + super(); + // boolean isJAXRS = isJAXRSResource(resourceClass); + // if (isJAXRS) { + if (resourceClass.isInterface()) { + this.resourceClass = generateResourceClass(resourceClass); + } else { + this.resourceClass = resourceClass; + } + // } else { + // throw new ServiceRuntimeException(resourceClass+" is not a JAX-RS resource class."); + // } + } + + @Override + public Set<Class<?>> getClasses() { + Set<Class<?>> classes = new HashSet<Class<?>>(); + classes.add(resourceClass); + return classes; + } + + private Class<?> generateResourceClass(Class<?> interfaze) { + try { + QName requestWireFormat = null; + if (binding.getRequestWireFormat() != null) { + requestWireFormat = binding.getRequestWireFormat().getSchemaName(); + } + QName responeWireFormat = null; + if (binding.getResponseWireFormat() != null) { + responeWireFormat = binding.getRequestWireFormat().getSchemaName(); + } + String requestMediaType = wireFormatToMediaTypeMapping.get(requestWireFormat); + String responseMediaType = wireFormatToMediaTypeMapping.get(responeWireFormat); + + String uri = endpoint.getBinding().getURI(); + String path = URI.create(uri).getPath(); + + // FIXME: [rfeng] We need to have a better way to deal with URI template for bindings + if(path.startsWith(servletHost.getContextPath())) { + path = path.substring(servletHost.getContextPath().length()); + } + Class<?> cls = + RootResourceClassGenerator.generateRootResourceClass(interfaze, + path, + requestMediaType, + responseMediaType); + ProxyFactory proxyFactory = ExtensibleProxyFactory.getInstance(extensionPoints); + Object proxy = proxyFactory.createProxy(interfaze, endpoint); + RootResourceClassGenerator.injectProxy(cls, proxy); + return cls; + } catch (Exception e) { + throw new ServiceRuntimeException(e); + } + } + + public void destroy() { + resourceClass = null; + } + } + + public static boolean isJAXRSResource(Class<?> cls) { + for (Annotation a : cls.getAnnotations()) { + if (a.annotationType().getName().startsWith("javax.ws.rs.")) { + return true; + } + } + for (Method method : cls.getMethods()) { + for (Annotation a : method.getAnnotations()) { + if (a.annotationType().getName().startsWith("javax.ws.rs.")) { + return true; + } + } + + /* + for (Annotation[] annotations : method.getParameterAnnotations()) { + for (Annotation a : annotations) { + if (a.annotationType().getName().startsWith("javax.ws.rs.")) { + return true; + } + } + + } + */ + } + return false; + } + + public void stop() { + if (application != null) { + application.destroy(); + } + // Unregister the Servlet from the Servlet host + servletHost.removeServletMapping(servletMapping); + } + + public InterfaceContract getBindingInterfaceContract() { + return serviceContract; + } + + public boolean supportsOneWayInvocation() { + return false; + } + + /** + * Add specific rest interceptor to invocation chain + */ + public void configure() { + + InvocationChain bindingChain = endpoint.getBindingInvocationChain(); + + if (wfProvider != null) { + Interceptor interceptor = wfProvider.createInterceptor(); + if (interceptor != null) { + bindingChain.addInterceptor(Phase.SERVICE_BINDING_WIREFORMAT, interceptor); + } + } + + if (osProvider != null) { + Interceptor interceptor = osProvider.createInterceptor(); + if (interceptor != null) { + bindingChain.addInterceptor(Phase.SERVICE_BINDING_OPERATION_SELECTOR, interceptor); + } + } + + } + +} diff --git a/sca-java-2.x/tags/2.0-M5.1/modules/binding-rest-runtime/src/main/java/org/apache/tuscany/sca/binding/rest/provider/RESTServiceListenerServlet.java b/sca-java-2.x/tags/2.0-M5.1/modules/binding-rest-runtime/src/main/java/org/apache/tuscany/sca/binding/rest/provider/RESTServiceListenerServlet.java new file mode 100644 index 0000000000..1dac8a2c9a --- /dev/null +++ b/sca-java-2.x/tags/2.0-M5.1/modules/binding-rest-runtime/src/main/java/org/apache/tuscany/sca/binding/rest/provider/RESTServiceListenerServlet.java @@ -0,0 +1,118 @@ +/* + * 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 org.apache.tuscany.sca.binding.rest.provider; + +import java.io.IOException; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; + +import javax.servlet.Servlet; +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.tuscany.sca.assembly.Binding; +import org.apache.tuscany.sca.binding.rest.RESTBinding; +import org.apache.tuscany.sca.common.http.HTTPCacheContext; +import org.apache.tuscany.sca.common.http.HTTPContext; +import org.apache.tuscany.sca.common.http.HTTPHeader; +import org.apache.tuscany.sca.invocation.Invoker; +import org.apache.tuscany.sca.invocation.Message; +import org.apache.tuscany.sca.invocation.MessageFactory; + +/** + * Servlet responsible for dispatching HTTP service requests to the + * target component implementation. + * + * @version $Rev$ $Date$ + */ +public class RESTServiceListenerServlet extends HttpServlet implements Servlet { + + private static final long serialVersionUID = -5543706958107836637L; + + transient private RESTBinding binding; + transient private ServletConfig config; + transient private MessageFactory messageFactory; + transient private Invoker serviceInvoker; + + /** + * Constructs a new HTTPServiceListenerServlet. + */ + public RESTServiceListenerServlet(Binding binding, Invoker serviceInvoker, MessageFactory messageFactory) { + this.binding = (RESTBinding) binding; + this.serviceInvoker = serviceInvoker; + this.messageFactory = messageFactory; + } + + public ServletConfig getServletConfig() { + return config; + } + + public String getServletInfo() { + return ""; + } + + public void init(ServletConfig config) throws ServletException { + this.config = config; + } + + public void destroy() { + + } + + @Override + public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + HTTPContext bindingContext = new HTTPContext(); + bindingContext.setHttpRequest(request); + bindingContext.setHttpResponse(response); + + // Dispatch the service interaction to the service invoker + Message requestMessage = messageFactory.createMessage(); + requestMessage.setBindingContext(bindingContext); + requestMessage.setBody(new Object[]{request, response}); + Message responseMessage = serviceInvoker.invoke(requestMessage); + if (responseMessage.isFault()) { + // Turn a fault into an exception + //throw new ServletException((Throwable)responseMessage.getBody()); + Throwable e = (Throwable)responseMessage.getBody(); + ((HttpServletResponse)response).sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.toString()); + } else { + //handles declarative headers configured on the composite + for(HTTPHeader header : binding.getHttpHeaders()) { + //treat special headers that need to be calculated + if(header.getName().equalsIgnoreCase("Expires")) { + GregorianCalendar calendar = new GregorianCalendar(); + calendar.setTime(new Date()); + + calendar.add(Calendar.HOUR, Integer.parseInt(header.getValue())); + + response.setHeader("Expires", HTTPCacheContext.RFC822DateFormat.format( calendar.getTime() )); + } else { + //default behaviour to pass the header value to HTTP response + response.setHeader(header.getName(), header.getValue()); + } + + } + } + } +} diff --git a/sca-java-2.x/tags/2.0-M5.1/modules/binding-rest-runtime/src/main/java/org/apache/tuscany/sca/binding/rest/provider/TuscanyRESTServlet.java b/sca-java-2.x/tags/2.0-M5.1/modules/binding-rest-runtime/src/main/java/org/apache/tuscany/sca/binding/rest/provider/TuscanyRESTServlet.java new file mode 100644 index 0000000000..23b951c88a --- /dev/null +++ b/sca-java-2.x/tags/2.0-M5.1/modules/binding-rest-runtime/src/main/java/org/apache/tuscany/sca/binding/rest/provider/TuscanyRESTServlet.java @@ -0,0 +1,127 @@ +/* + * 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 org.apache.tuscany.sca.binding.rest.provider; + +import java.io.IOException; +import java.util.Enumeration; + +import javax.servlet.FilterConfig; +import javax.servlet.ServletContext; +import javax.ws.rs.HttpMethod; +import javax.ws.rs.core.MediaType; + +import org.apache.tuscany.sca.core.ExtensionPointRegistry; +import org.apache.wink.common.internal.registry.ProvidersRegistry; +import org.apache.wink.common.internal.registry.metadata.MethodMetadata; +import org.apache.wink.server.internal.DeploymentConfiguration; +import org.apache.wink.server.internal.RequestProcessor; +import org.apache.wink.server.internal.registry.ResourceRecord; +import org.apache.wink.server.internal.servlet.RestServlet; + +/** + * + */ +public class TuscanyRESTServlet extends RestServlet { + private static final long serialVersionUID = 89997233133964915L; + private ExtensionPointRegistry registry; + private Class<?> resourceClass; + private boolean fixed; + + public TuscanyRESTServlet(ExtensionPointRegistry registry, Class<?> resourceClass) { + super(); + this.registry = registry; + this.resourceClass = resourceClass; + } + + @Override + public DeploymentConfiguration getDeploymentConfiguration() throws ClassNotFoundException, InstantiationException, + IllegalAccessException, IOException { + DeploymentConfiguration config = super.getDeploymentConfiguration(); + + // [rfeng] FIXME: This is a hack to fool Apache wink to not remove the servlet path + config.setFilterConfig(new FilterConfig() { + + public ServletContext getServletContext() { + return getServletContext(); + } + + public Enumeration getInitParameterNames() { + return getInitParameterNames(); + } + + public String getInitParameter(String arg0) { + return getInitParameter(arg0); + } + + public String getFilterName() { + return getServletName(); + } + }); + + ProvidersRegistry providers = config.getProvidersRegistry(); + providers.addProvider(new DataBindingJAXRSReader(registry), 0.001, true); + providers.addProvider(new DataBindingJAXRSWriter(registry), 0.001, true); + + return config; + } + + private synchronized void fixMediaTypes(DeploymentConfiguration config) { + if (fixed) { + return; + } + // FIXME: A hacky workaround for https://issues.apache.org/jira/browse/TUSCANY-3572 + ResourceRecord record = config.getResourceRegistry().getRecord(resourceClass); + + for (MethodMetadata methodMetadata : record.getMetadata().getResourceMethods()) { + String method = methodMetadata.getHttpMethod(); + if (HttpMethod.GET.equals(method) || HttpMethod.HEAD.equals(method) || HttpMethod.DELETE.equals(method)) { + methodMetadata.addConsumes(MediaType.APPLICATION_OCTET_STREAM_TYPE); + methodMetadata.addConsumes(MediaType.WILDCARD_TYPE); + } + if (HttpMethod.HEAD.equals(method) || HttpMethod.DELETE.equals(method)) { + methodMetadata.addProduces(MediaType.APPLICATION_OCTET_STREAM_TYPE); + methodMetadata.addConsumes(MediaType.WILDCARD_TYPE); + } + } + for (MethodMetadata methodMetadata : record.getMetadata().getSubResourceMethods()) { + String method = methodMetadata.getHttpMethod(); + if (HttpMethod.GET.equals(method) || HttpMethod.HEAD.equals(method) || HttpMethod.DELETE.equals(method)) { + methodMetadata.addConsumes(MediaType.APPLICATION_OCTET_STREAM_TYPE); + methodMetadata.addConsumes(MediaType.WILDCARD_TYPE); + } + if (HttpMethod.HEAD.equals(method) || HttpMethod.DELETE.equals(method)) { + methodMetadata.addProduces(MediaType.APPLICATION_OCTET_STREAM_TYPE); + methodMetadata.addConsumes(MediaType.WILDCARD_TYPE); + } + } + fixed = true; + } + + @Override + public RequestProcessor getRequestProcessor() { + RequestProcessor processor = super.getRequestProcessor(); + // The 1st call returns null + if (processor != null) { + fixMediaTypes(processor.getConfiguration()); + } + return processor; + } + +} |