You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by se...@apache.org on 2009/04/09 14:10:09 UTC
svn commit: r763616 - in /cxf/trunk:
rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/
rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/
rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/interceptor/
rt/frontend/jaxrs/src/main/ja...
Author: sergeyb
Date: Thu Apr 9 12:10:08 2009
New Revision: 763616
URL: http://svn.apache.org/viewvc?rev=763616&view=rev
Log:
JAXRS: adding a missing test file, support for HEAD/OPTIONS, improving per-request handling
Added:
cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/BookApplication.java (with props)
Modified:
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/AbstractClient.java
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/HttpHeadersImpl.java
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseBuilderImpl.java
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/interceptor/JAXRSInInterceptor.java
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/interceptor/JAXRSOutInterceptor.java
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/interceptor/Messages.properties
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/lifecycle/PerRequestResourceProvider.java
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/servlet/CXFNonSpringJaxrsServlet.java
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/AnnotationUtils.java
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/HttpUtils.java
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/Messages.properties
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ResourceUtils.java
cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/Customer.java
cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/HttpHeadersImplTest.java
cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/ResponseBuilderImplTest.java
cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriInfoImplTest.java
cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/lifecycle/PerRequestResourceProviderTest.java
cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/utils/HttpUtilsTest.java
cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/utils/ResourceUtilsTest.java
cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HTTPConduit.java
cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/BookStorePerRequest.java
cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSClientServerBookTest.java
cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSClientServerNonSpringBookTest.java
Modified: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/AbstractClient.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/AbstractClient.java?rev=763616&r1=763615&r2=763616&view=diff
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/AbstractClient.java (original)
+++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/client/AbstractClient.java Thu Apr 9 12:10:08 2009
@@ -304,8 +304,19 @@
int status = conn.getResponseCode();
responseBuilder = Response.status(status);
for (Map.Entry<String, List<String>> entry : conn.getHeaderFields().entrySet()) {
- for (String s : entry.getValue()) {
- responseBuilder.header(entry.getKey(), s);
+ if (null == entry.getKey()) {
+ continue;
+ }
+ if (HttpUtils.isDateRelatedHeader(entry.getKey())) {
+ responseBuilder.header(entry.getKey(), entry.getValue());
+ } else if (entry.getValue().size() > 0) {
+ String[] values = entry.getValue().get(0).split(",");
+ for (String s : values) {
+ String theValue = s.trim();
+ if (theValue.length() > 0) {
+ responseBuilder.header(entry.getKey(), theValue);
+ }
+ }
}
}
if (status >= 400) {
Modified: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/HttpHeadersImpl.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/HttpHeadersImpl.java?rev=763616&r1=763615&r2=763616&view=diff
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/HttpHeadersImpl.java (original)
+++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/HttpHeadersImpl.java Thu Apr 9 12:10:08 2009
@@ -34,6 +34,7 @@
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
+import org.apache.cxf.jaxrs.utils.HttpUtils;
import org.apache.cxf.jaxrs.utils.JAXRSUtils;
import org.apache.cxf.message.Message;
@@ -123,6 +124,9 @@
if (values == null || values.isEmpty()) {
return Collections.emptyList();
}
+ if (HttpUtils.isDateRelatedHeader(headerName)) {
+ return values;
+ }
String[] ls = values.get(0).split(",");
if (ls.length == 1) {
return Collections.singletonList(ls[0].trim());
Modified: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseBuilderImpl.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseBuilderImpl.java?rev=763616&r1=763615&r2=763616&view=diff
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseBuilderImpl.java (original)
+++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/ResponseBuilderImpl.java Thu Apr 9 12:10:08 2009
@@ -125,22 +125,21 @@
}
public ResponseBuilder header(String name, Object value) {
- return addHeader(name, value);
+ if (HttpUtils.isDateRelatedHeader(name)) {
+ Object theValue = value instanceof Date ? toHttpDate((Date)value) : value;
+ return setHeader(name, theValue);
+ } else {
+ return addHeader(name, value);
+ }
}
@Override
public ResponseBuilder variant(Variant variant) {
- if (variant.getMediaType() != null) {
- type(variant.getMediaType());
- }
- if (variant.getLanguage() != null) {
- language(variant.getLanguage());
- }
- if (variant.getEncoding() != null) {
- metadata.putSingle(HttpHeaders.CONTENT_ENCODING,
- variant.getEncoding());
- }
+ type(variant == null ? null : variant.getMediaType());
+ language(variant == null ? null : variant.getLanguage());
+ setHeader(HttpHeaders.CONTENT_ENCODING, variant == null ? null : variant.getEncoding());
+
return this;
}
Modified: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/interceptor/JAXRSInInterceptor.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/interceptor/JAXRSInInterceptor.java?rev=763616&r1=763615&r2=763616&view=diff
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/interceptor/JAXRSInInterceptor.java (original)
+++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/interceptor/JAXRSInInterceptor.java Thu Apr 9 12:10:08 2009
@@ -132,15 +132,26 @@
acceptTypes = newAcceptTypes;
acceptContentTypes = JAXRSUtils.sortMediaTypes(newAcceptTypes);
message.getExchange().put(Message.ACCEPT_CONTENT_TYPE, acceptContentTypes);
-
if (ori != null) {
values = new MetadataMap<String, String>();
resource = JAXRSUtils.selectResourceClass(resources,
rawPath,
values);
}
- ori = JAXRSUtils.findTargetMethod(resource, values.getFirst(URITemplate.FINAL_MATCH_GROUP),
- httpMethod, values, requestContentType, acceptContentTypes);
+ try {
+ ori = JAXRSUtils.findTargetMethod(resource,
+ values.getFirst(URITemplate.FINAL_MATCH_GROUP), httpMethod, values,
+ requestContentType, acceptContentTypes);
+ } catch (WebApplicationException ex) {
+ if (ex.getResponse() != null && ex.getResponse().getStatus() == 405
+ && "OPTIONS".equalsIgnoreCase(httpMethod)) {
+ Response response = JAXRSUtils.createResponseBuilder(resource, 200, true).build();
+ message.getExchange().put(Response.class, response);
+ return;
+ } else {
+ throw ex;
+ }
+ }
message.getExchange().put(OperationResourceInfo.class, ori);
}
Response response = sh.getProvider().handleRequest(message, resource);
Modified: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/interceptor/JAXRSOutInterceptor.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/interceptor/JAXRSOutInterceptor.java?rev=763616&r1=763615&r2=763616&view=diff
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/interceptor/JAXRSOutInterceptor.java (original)
+++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/interceptor/JAXRSOutInterceptor.java Thu Apr 9 12:10:08 2009
@@ -125,6 +125,12 @@
boolean firstTry) {
int status = response.getStatus();
Object responseObj = response.getEntity();
+ if (status == 200 && responseObj != null && firstTry
+ && JAXRSUtils.headMethodPossible(ori.getHttpMethod(),
+ (String)message.getExchange().getInMessage().get(Message.HTTP_REQUEST_METHOD))) {
+ LOG.info(new org.apache.cxf.common.i18n.Message("HEAD_WITHOUT_ENTITY", BUNDLE).toString());
+ responseObj = null;
+ }
if (status == -1) {
status = responseObj == null ? 204 : 200;
}
Modified: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/interceptor/Messages.properties
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/interceptor/Messages.properties?rev=763616&r1=763615&r2=763616&view=diff
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/interceptor/Messages.properties (original)
+++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/interceptor/Messages.properties Thu Apr 9 12:10:08 2009
@@ -20,4 +20,5 @@
#
SERIALIZE_ERROR =.Error serializing the response, please check the server logs, response class : {0}.
NO_MSG_WRITER =.No message body writer found for response class : {0}.
-NO_ROOT_EXC =.No root resource matching request path {0} is found.
\ No newline at end of file
+NO_ROOT_EXC =.No root resource matching request path {0} is found.
+HEAD_WITHOUT_ENTITY = Entity body returned by GET-supporting resource method will be dropped as HEAD was the actual http request method
\ No newline at end of file
Modified: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/lifecycle/PerRequestResourceProvider.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/lifecycle/PerRequestResourceProvider.java?rev=763616&r1=763615&r2=763616&view=diff
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/lifecycle/PerRequestResourceProvider.java (original)
+++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/lifecycle/PerRequestResourceProvider.java Thu Apr 9 12:10:08 2009
@@ -19,13 +19,17 @@
package org.apache.cxf.jaxrs.lifecycle;
+import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Type;
import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
+import org.apache.cxf.jaxrs.model.URITemplate;
+import org.apache.cxf.jaxrs.utils.AnnotationUtils;
import org.apache.cxf.jaxrs.utils.JAXRSUtils;
import org.apache.cxf.jaxrs.utils.ResourceUtils;
import org.apache.cxf.message.Message;
@@ -34,7 +38,7 @@
private Constructor<?> c;
public PerRequestResourceProvider(Class<?> clazz) {
- c = ResourceUtils.findResourceConstructor(clazz);
+ c = ResourceUtils.findResourceConstructor(clazz, true);
if (c == null) {
throw new RuntimeException("Resource class " + clazz
+ " has no valid constructor");
@@ -57,13 +61,22 @@
return createInstance(null);
}
+ @SuppressWarnings("unchecked")
protected Object createInstance(Message m) {
Class<?>[] params = c.getParameterTypes();
+ Annotation[][] anns = c.getParameterAnnotations();
Type[] genericTypes = c.getGenericParameterTypes();
- Object[] values = new Object[]{params.length};
+ MultivaluedMap<String, String> templateValues =
+ (MultivaluedMap)m.get(URITemplate.TEMPLATE_PARAMETERS);
+ Object[] values = new Object[params.length];
for (int i = 0; i < params.length; i++) {
- values[i] = JAXRSUtils.createContextValue(m, genericTypes[i], params[i]);
+ if (AnnotationUtils.isContextClass(params[i])) {
+ values[i] = JAXRSUtils.createContextValue(m, genericTypes[i], params[i]);
+ } else {
+ values[i] = JAXRSUtils.createHttpParameterValue(
+ anns[i], params[i], genericTypes[i], m, templateValues, null);
+ }
}
try {
return params.length > 0 ? c.newInstance(values) : c.newInstance(new Object[]{});
Modified: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/servlet/CXFNonSpringJaxrsServlet.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/servlet/CXFNonSpringJaxrsServlet.java?rev=763616&r1=763615&r2=763616&view=diff
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/servlet/CXFNonSpringJaxrsServlet.java (original)
+++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/servlet/CXFNonSpringJaxrsServlet.java Thu Apr 9 12:10:08 2009
@@ -90,13 +90,8 @@
List<Class> resourceClasses = new ArrayList<Class>();
for (String cName : classNames) {
if (cName.length() != 0) {
- try {
- Class<?> cls = CXFNonSpringJaxrsServlet.class.getClassLoader().loadClass(cName.trim());
- resourceClasses.add(cls);
- } catch (ClassNotFoundException ex) {
- ex.printStackTrace();
- throw new ServletException("Resource class " + cName.trim() + " can not be loaded");
- }
+ Class<?> cls = loadClass(cName);
+ resourceClasses.add(cls);
}
}
if (resourceClasses.isEmpty()) {
@@ -140,7 +135,7 @@
private Object createSingletonInstance(Class<?> cls, ServletConfig sc) throws ServletException {
- Constructor c = ResourceUtils.findResourceConstructor(cls);
+ Constructor c = ResourceUtils.findResourceConstructor(cls, false);
if (c == null) {
throw new ServletException("No valid constructor found for " + cls.getName());
}
@@ -220,7 +215,7 @@
try {
return ClassUtils.getClass(CXFNonSpringJaxrsServlet.class.getClassLoader(), cName.trim());
} catch (ClassNotFoundException ex) {
- throw new ServletException("Resource class " + cName.trim() + " can not be loaded", ex);
+ throw new ServletException("No resource class " + cName.trim() + " can be found", ex);
}
}
}
Modified: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/AnnotationUtils.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/AnnotationUtils.java?rev=763616&r1=763615&r2=763616&view=diff
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/AnnotationUtils.java (original)
+++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/AnnotationUtils.java Thu Apr 9 12:10:08 2009
@@ -112,14 +112,14 @@
return PARAM_ANNOTATION_CLASSES.contains(annotationClass);
}
- public static boolean isMethodParamAnnotationClass(Class<?> annotationClass) {
+ public static boolean isValidParamAnnotationClass(Class<?> annotationClass) {
return PARAM_ANNOTATION_CLASSES.contains(annotationClass)
|| Context.class == annotationClass;
}
- public static boolean isMethodParamAnnotations(Annotation[] paramAnnotations) {
+ public static boolean isValidParamAnnotations(Annotation[] paramAnnotations) {
for (Annotation a : paramAnnotations) {
- if (AnnotationUtils.isMethodParamAnnotationClass(a.annotationType())) {
+ if (AnnotationUtils.isValidParamAnnotationClass(a.annotationType())) {
return true;
}
}
@@ -178,7 +178,7 @@
}
}
for (Annotation[] paramAnnotations : m.getParameterAnnotations()) {
- if (isMethodParamAnnotations(paramAnnotations)) {
+ if (isValidParamAnnotations(paramAnnotations)) {
return m;
}
}
Modified: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/HttpUtils.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/HttpUtils.java?rev=763616&r1=763615&r2=763616&view=diff
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/HttpUtils.java (original)
+++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/HttpUtils.java Thu Apr 9 12:10:08 2009
@@ -33,6 +33,7 @@
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.PathSegment;
import javax.ws.rs.core.Response;
@@ -119,6 +120,14 @@
return dateFormat;
}
+ public static boolean isDateRelatedHeader(String headerName) {
+ return HttpHeaders.DATE.equalsIgnoreCase(headerName)
+ || HttpHeaders.IF_MODIFIED_SINCE.equalsIgnoreCase(headerName)
+ || HttpHeaders.IF_UNMODIFIED_SINCE.equalsIgnoreCase(headerName)
+ || HttpHeaders.EXPIRES.equalsIgnoreCase(headerName)
+ || HttpHeaders.LAST_MODIFIED.equalsIgnoreCase(headerName);
+ }
+
public static URI toAbsoluteUri(URI u, Message message) {
if (!u.isAbsolute()) {
HttpServletRequest httpRequest =
Modified: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java?rev=763616&r1=763615&r2=763616&view=diff
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java (original)
+++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java Thu Apr 9 12:10:08 2009
@@ -256,7 +256,7 @@
for (MediaType acceptType : acceptContentTypes) {
for (OperationResourceInfo ori : resource.getMethodDispatcher().getOperationResourceInfos()) {
URITemplate uriTemplate = ori.getURITemplate();
- MultivaluedMap<String, String> map = cloneMap(values);
+ MultivaluedMap<String, String> map = new MetadataMap<String, String>(values);
if (uriTemplate != null && uriTemplate.match(path, map)) {
if (ori.isSubResourceLocator()) {
candidateList.put(ori, map);
@@ -265,7 +265,7 @@
if (finalGroup == null || StringUtils.isEmpty(finalGroup)
|| finalGroup.equals("/")) {
pathMatched++;
- boolean mMatched = ori.getHttpMethod().equalsIgnoreCase(httpMethod);
+ boolean mMatched = matchHttpMethod(ori.getHttpMethod(), httpMethod);
boolean cMatched = matchConsumeTypes(requestType, ori);
boolean pMatched = matchProduceTypes(acceptType, ori);
if (mMatched && cMatched && pMatched) {
@@ -289,7 +289,13 @@
candidateList.entrySet().iterator().next();
values.clear();
values.putAll(firstEntry.getValue());
- return firstEntry.getKey();
+ OperationResourceInfo ori = firstEntry.getKey();
+ if (headMethodPossible(ori.getHttpMethod(), httpMethod)) {
+ LOG.info(new org.apache.cxf.common.i18n.Message("GET_INSTEAD_OF_HEAD",
+ BUNDLE, resource.getServiceClass().getName(),
+ ori.getMethodToInvoke().getName()).toString());
+ }
+ return ori;
}
int status = pathMatched == 0 ? 404 : methodMatched == 0 ? 405
@@ -301,17 +307,36 @@
path,
requestType.toString(),
convertTypesToString(acceptContentTypes));
- LOG.warning(errorMsg.toString());
- ResponseBuilder rb = Response.status(status);
- if (methodMatched == 0) {
- for (String m : resource.getAllowedMethods()) {
- rb.header("Allow", m);
- }
+ if (!"OPTIONS".equalsIgnoreCase(httpMethod)) {
+ LOG.warning(errorMsg.toString());
}
+ ResponseBuilder rb = createResponseBuilder(resource, status, methodMatched == 0);
throw new WebApplicationException(rb.build());
}
+ public static ResponseBuilder createResponseBuilder(ClassResourceInfo cri, int status, boolean addAllow) {
+ ResponseBuilder rb = Response.status(status);
+ if (addAllow) {
+ for (String m : cri.getAllowedMethods()) {
+ rb.header("Allow", m);
+ }
+ }
+ return rb;
+ }
+
+ private static boolean matchHttpMethod(String expectedMethod, String httpMethod) {
+ if (expectedMethod.equalsIgnoreCase(httpMethod)
+ || headMethodPossible(expectedMethod, httpMethod)) {
+ return true;
+ }
+ return false;
+ }
+
+ public static boolean headMethodPossible(String expectedMethod, String httpMethod) {
+ return "HEAD".equalsIgnoreCase(httpMethod) && "GET".equals(expectedMethod);
+ }
+
private static String convertTypesToString(List<MediaType> types) {
StringBuilder sb = new StringBuilder();
for (MediaType type : types) {
@@ -421,7 +446,7 @@
InputStream is = message.getContent(InputStream.class);
if (parameterAnns == null
- || !AnnotationUtils.isMethodParamAnnotations(parameterAnns)) {
+ || !AnnotationUtils.isValidParamAnnotations(parameterAnns)) {
String contentType = (String)message.get(Message.CONTENT_TYPE);
@@ -454,7 +479,7 @@
}
}
- private static Object createHttpParameterValue(Annotation[] anns,
+ public static Object createHttpParameterValue(Annotation[] anns,
Class<?> parameterClass,
Type genericParam,
Message message,
@@ -954,17 +979,6 @@
return types;
}
-
- private static <K, V> MultivaluedMap<K, V> cloneMap(MultivaluedMap<K, V> map1) {
-
- MultivaluedMap<K, V> map2 = new MetadataMap<K, V>();
- for (Map.Entry<K, List<V>> entry : map1.entrySet()) {
- map2.put(entry.getKey(), new ArrayList<V>(entry.getValue()));
- }
- return map2;
-
- }
-
@SuppressWarnings("unchecked")
public static Response convertFaultToResponse(Throwable ex, Message inMessage) {
Modified: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/Messages.properties
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/Messages.properties?rev=763616&r1=763615&r2=763616&view=diff
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/Messages.properties (original)
+++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/Messages.properties Thu Apr 9 12:10:08 2009
@@ -19,6 +19,7 @@
#
#
NO_RESOURCE_OP_EXC=No resource methods found for resource class {0}
+GET_INSTEAD_OF_HEAD=Resource class {0} does not support HEAD http method, method {1} supporting GET http method will be invoked
NO_CONTENT_TYPE_SPECIFIED=No Content-Type specified for HTTP {0}
METHOD_INJECTION_FAILURE=Method {0} injection failure
METHOD_ACCESS_FAILURE=Method {0} can not be accessed due to security manager restrictions
Modified: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ResourceUtils.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ResourceUtils.java?rev=763616&r1=763615&r2=763616&view=diff
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ResourceUtils.java (original)
+++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ResourceUtils.java Thu Apr 9 12:10:08 2009
@@ -22,6 +22,10 @@
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.List;
import java.util.ResourceBundle;
import java.util.logging.Logger;
@@ -89,29 +93,38 @@
cri.setMethodDispatcher(md);
}
- public static Constructor findResourceConstructor(Class<?> resourceClass) {
- Constructor defaultConstructor = null;
+ public static Constructor findResourceConstructor(Class<?> resourceClass, boolean perRequest) {
+ List<Constructor> cs = new LinkedList<Constructor>();
for (Constructor c : resourceClass.getConstructors()) {
Class<?>[] params = c.getParameterTypes();
- if (params.length == 0) {
- defaultConstructor = c;
- continue;
- }
-
Annotation[][] anns = c.getParameterAnnotations();
boolean match = true;
for (int i = 0; i < params.length; i++) {
- if (AnnotationUtils.getAnnotation(anns[i], Context.class) == null
- || !AnnotationUtils.isContextClass(params[i])) {
+ if (!perRequest) {
+ if (AnnotationUtils.getAnnotation(anns[i], Context.class) == null
+ || !AnnotationUtils.isContextClass(params[i])) {
+ match = false;
+ break;
+ }
+ } else if (!AnnotationUtils.isValidParamAnnotations(anns[i])) {
match = false;
break;
}
}
if (match) {
- return c;
+ cs.add(c);
}
}
- return defaultConstructor;
+ Collections.sort(cs, new Comparator<Constructor>() {
+
+ public int compare(Constructor c1, Constructor c2) {
+ int p1 = c1.getParameterTypes().length;
+ int p2 = c2.getParameterTypes().length;
+ return p1 > p2 ? -1 : p1 < p2 ? 1 : 0;
+ }
+
+ });
+ return cs.size() == 0 ? null : cs.get(0);
}
private static OperationResourceInfo createOperationInfo(Method m, Method annotatedMethod,
Modified: cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/Customer.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/Customer.java?rev=763616&r1=763615&r2=763616&view=diff
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/Customer.java (original)
+++ cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/Customer.java Thu Apr 9 12:10:08 2009
@@ -117,6 +117,12 @@
uriInfo = info;
}
+ public Customer(@Context UriInfo info,
+ @QueryParam("a") String queryParam) {
+ uriInfo = info;
+ this.queryParam = queryParam;
+ }
+
public String getName() {
return name;
}
Modified: cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/HttpHeadersImplTest.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/HttpHeadersImplTest.java?rev=763616&r1=763615&r2=763616&view=diff
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/HttpHeadersImplTest.java (original)
+++ cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/HttpHeadersImplTest.java Thu Apr 9 12:10:08 2009
@@ -90,6 +90,9 @@
assertEquals(1, contentValues.size());
assertEquals("*/*", contentValues.get(0));
+ List<String> dateValues = h.getRequestHeader("Date");
+ assertEquals(1, dateValues.size());
+ assertEquals("Tue, 21 Oct 2008 17:00:00 GMT", dateValues.get(0));
}
@Test
@@ -168,6 +171,7 @@
MetadataMap<String, String> hs = new MetadataMap<String, String>();
hs.putSingle("Accept", "text/*;q=1,application/xml");
hs.putSingle("Content-Type", "*/*");
+ hs.putSingle("Date", "Tue, 21 Oct 2008 17:00:00 GMT");
return hs;
}
Modified: cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/ResponseBuilderImplTest.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/ResponseBuilderImplTest.java?rev=763616&r1=763615&r2=763616&view=diff
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/ResponseBuilderImplTest.java (original)
+++ cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/ResponseBuilderImplTest.java Thu Apr 9 12:10:08 2009
@@ -79,6 +79,9 @@
SimpleDateFormat format = HttpUtils.getHttpDateFormat();
Date date = format.parse("Tue, 21 Oct 2008 17:00:00 GMT");
checkBuild(Response.ok().expires(date).build(), 200, null, m);
+ checkBuild(Response.ok().expires(date)
+ .header(HttpHeaders.EXPIRES, date).build(), 200, null, m);
+ checkBuild(Response.ok().header(HttpHeaders.EXPIRES, date).build(), 200, null, m);
}
@Test
Modified: cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriInfoImplTest.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriInfoImplTest.java?rev=763616&r1=763615&r2=763616&view=diff
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriInfoImplTest.java (original)
+++ cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriInfoImplTest.java Thu Apr 9 12:10:08 2009
@@ -42,6 +42,7 @@
@Before
public void setUp() {
control = EasyMock.createNiceControl();
+ control.makeThreadSafe(true);
}
@Test
Modified: cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/lifecycle/PerRequestResourceProviderTest.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/lifecycle/PerRequestResourceProviderTest.java?rev=763616&r1=763615&r2=763616&view=diff
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/lifecycle/PerRequestResourceProviderTest.java (original)
+++ cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/lifecycle/PerRequestResourceProviderTest.java Thu Apr 9 12:10:08 2009
@@ -19,6 +19,7 @@
package org.apache.cxf.jaxrs.lifecycle;
import org.apache.cxf.jaxrs.Customer;
+import org.apache.cxf.message.Message;
import org.apache.cxf.message.MessageImpl;
import org.junit.Assert;
@@ -29,8 +30,11 @@
@Test
public void testGetInstance() {
PerRequestResourceProvider rp = new PerRequestResourceProvider(Customer.class);
- Customer c = (Customer)rp.getInstance(new MessageImpl());
+ Message message = new MessageImpl();
+ message.put(Message.QUERY_STRING, "a=aValue");
+ Customer c = (Customer)rp.getInstance(message);
assertNotNull(c.getUriInfo());
+ assertEquals("aValue", c.getQueryParam());
}
}
Modified: cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/utils/HttpUtilsTest.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/utils/HttpUtilsTest.java?rev=763616&r1=763615&r2=763616&view=diff
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/utils/HttpUtilsTest.java (original)
+++ cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/utils/HttpUtilsTest.java Thu Apr 9 12:10:08 2009
@@ -19,6 +19,7 @@
package org.apache.cxf.jaxrs.utils;
+import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import org.apache.cxf.message.ExchangeImpl;
@@ -36,6 +37,16 @@
}
@Test
+ public void testIsDateHeader() {
+ assertFalse(HttpUtils.isDateRelatedHeader(HttpHeaders.ETAG));
+ assertTrue(HttpUtils.isDateRelatedHeader(HttpHeaders.EXPIRES));
+ assertTrue(HttpUtils.isDateRelatedHeader(HttpHeaders.IF_MODIFIED_SINCE));
+ assertTrue(HttpUtils.isDateRelatedHeader(HttpHeaders.IF_UNMODIFIED_SINCE));
+ assertTrue(HttpUtils.isDateRelatedHeader(HttpHeaders.DATE));
+ assertTrue(HttpUtils.isDateRelatedHeader(HttpHeaders.LAST_MODIFIED));
+ }
+
+ @Test
public void testUrlEncode() {
assertEquals("%2B+", HttpUtils.urlEncode("+ "));
}
Modified: cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/utils/ResourceUtilsTest.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/utils/ResourceUtilsTest.java?rev=763616&r1=763615&r2=763616&view=diff
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/utils/ResourceUtilsTest.java (original)
+++ cxf/trunk/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/utils/ResourceUtilsTest.java Thu Apr 9 12:10:08 2009
@@ -31,10 +31,11 @@
@Test
public void testFindResourceConstructor() {
- Constructor c = ResourceUtils.findResourceConstructor(Customer.class);
+ Constructor c = ResourceUtils.findResourceConstructor(Customer.class, true);
assertNotNull(c);
- assertEquals(1, c.getParameterTypes().length);
+ assertEquals(2, c.getParameterTypes().length);
assertEquals(UriInfo.class, c.getParameterTypes()[0]);
+ assertEquals(String.class, c.getParameterTypes()[1]);
}
Modified: cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HTTPConduit.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HTTPConduit.java?rev=763616&r1=763615&r2=763616&view=diff
==============================================================================
--- cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HTTPConduit.java (original)
+++ cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HTTPConduit.java Thu Apr 9 12:10:08 2009
@@ -1870,10 +1870,10 @@
// If this is a GET method we must not touch the output
// stream as this automatically turns the request into a POST.
- // Nor it should be done in case of DELETE - strangely, empty PUTs
- // work ok
- if ("GET".equals(connection.getRequestMethod())
- || "DELETE".equals(connection.getRequestMethod())) {
+ // Nor it should be done in case of DELETE/HEAD/OPTIONS
+ // - strangely, empty PUTs work ok
+ if (!"POST".equals(connection.getRequestMethod())
+ || !"PUT".equals(connection.getRequestMethod())) {
return;
}
Added: cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/BookApplication.java
URL: http://svn.apache.org/viewvc/cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/BookApplication.java?rev=763616&view=auto
==============================================================================
--- cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/BookApplication.java (added)
+++ cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/BookApplication.java Thu Apr 9 12:10:08 2009
@@ -0,0 +1,43 @@
+/**
+ * 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.cxf.systest.jaxrs;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.ws.rs.core.Application;
+
+public class BookApplication extends Application {
+
+ @Override
+ public Set<Class<?>> getClasses() {
+ Set<Class<?>> classes = new HashSet<Class<?>>();
+ classes.add(org.apache.cxf.systest.jaxrs.BookStorePerRequest.class);
+ return classes;
+ }
+
+ @Override
+ public Set<Object> getSingletons() {
+ Set<Object> classes = new HashSet<Object>();
+ classes.add(new org.apache.cxf.systest.jaxrs.BookStore());
+ return classes;
+ }
+
+
+}
Propchange: cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/BookApplication.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/BookApplication.java
------------------------------------------------------------------------------
svn:keywords = Rev Date
Modified: cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/BookStorePerRequest.java
URL: http://svn.apache.org/viewvc/cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/BookStorePerRequest.java?rev=763616&r1=763615&r2=763616&view=diff
==============================================================================
--- cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/BookStorePerRequest.java (original)
+++ cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/BookStorePerRequest.java Thu Apr 9 12:10:08 2009
@@ -25,6 +25,7 @@
import java.util.Map;
import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
import javax.ws.rs.Path;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
@@ -34,9 +35,24 @@
private HttpHeaders httpHeaders;
private Map<Long, Book> books = new HashMap<Long, Book>();
+ private List<String> bookIds;
+
+ public BookStorePerRequest() {
+ throw new RuntimeException();
+ }
public BookStorePerRequest(@Context HttpHeaders headers) {
- httpHeaders = headers;
+ throw new RuntimeException();
+ }
+
+ public BookStorePerRequest(@Context HttpHeaders headers, Long bar) {
+ throw new RuntimeException();
+ }
+
+ public BookStorePerRequest(@Context HttpHeaders headers,
+ @HeaderParam("BOOK") List<String> bookIds) {
+ httpHeaders = headers;
+ this.bookIds = bookIds;
init();
}
@@ -45,6 +61,9 @@
public Book getBookByHeader() throws Exception {
List<String> ids = httpHeaders.getRequestHeader("BOOK");
+ if (!ids.equals(bookIds)) {
+ throw new RuntimeException();
+ }
return doGetBook(ids.get(0) + ids.get(1) + ids.get(2));
}
Modified: cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSClientServerBookTest.java
URL: http://svn.apache.org/viewvc/cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSClientServerBookTest.java?rev=763616&r1=763615&r2=763616&view=diff
==============================================================================
--- cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSClientServerBookTest.java (original)
+++ cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSClientServerBookTest.java Thu Apr 9 12:10:08 2009
@@ -23,6 +23,7 @@
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
+import java.util.List;
import javax.ws.rs.core.Response;
@@ -48,7 +49,7 @@
@BeforeClass
public static void startServers() throws Exception {
assertTrue("server did not launch correctly",
- launchServer(BookServer.class));
+ launchServer(BookServer.class, true));
}
@Test
@@ -66,6 +67,27 @@
}
@Test
+ public void testHeadBookByURL() throws Exception {
+ WebClient wc =
+ WebClient.create("http://localhost:9080/bookstore/bookurl/http%3A%2F%2Ftest.com%2Frss%2F123");
+ Response response = wc.head();
+ assertTrue(response.getMetadata().size() != 0);
+ assertEquals(0, ((InputStream)response.getEntity()).available());
+ }
+
+ @Test
+ public void testOptions() throws Exception {
+ WebClient wc =
+ WebClient.create("http://localhost:9080/bookstore/bookurl/http%3A%2F%2Ftest.com%2Frss%2F123");
+ Response response = wc.options();
+ List<Object> values = response.getMetadata().get("Allow");
+ assertNotNull(values);
+ assertTrue(values.contains("POST") && values.contains("GET")
+ && values.contains("DELETE") && values.contains("PUT"));
+ assertEquals(0, ((InputStream)response.getEntity()).available());
+ }
+
+ @Test
public void testGetBookByEncodedQuery() throws Exception {
getAndCompareAsStrings("http://localhost:9080/bookstore/bookquery?"
+ "urlid=http%3A%2F%2Ftest.com%2Frss%2F123",
Modified: cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSClientServerNonSpringBookTest.java
URL: http://svn.apache.org/viewvc/cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSClientServerNonSpringBookTest.java?rev=763616&r1=763615&r2=763616&view=diff
==============================================================================
--- cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSClientServerNonSpringBookTest.java (original)
+++ cxf/trunk/systests/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSClientServerNonSpringBookTest.java Thu Apr 9 12:10:08 2009
@@ -36,7 +36,7 @@
@BeforeClass
public static void startServers() throws Exception {
assertTrue("server did not launch correctly",
- launchServer(BookNonSpringServer.class, true));
+ launchServer(BookNonSpringServer.class));
}
@@ -49,7 +49,6 @@
}
@Test
- @org.junit.Ignore
public void testGetBook123ApplicationSingleton() throws Exception {
getAndCompareAsStrings("http://localhost:9080/application/bookstore/books/123",
"resources/expected_get_book123.txt",
@@ -58,7 +57,6 @@
}
@Test
- @org.junit.Ignore
public void testGetBook123ApplicationPerRequest() throws Exception {
getAndCompareAsStrings("http://localhost:9080/application/bookstore2/bookheaders",
"resources/expected_get_book123.txt",