/*******************************************************************************
* 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.wink.server.internal.registry;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.StringTokenizer;
import javax.servlet.ServletContext;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.MediaType;
import org.apache.wink.common.internal.application.ApplicationFileLoader;
import org.apache.wink.common.internal.providers.entity.StringProvider;
import org.apache.wink.common.internal.registry.ProvidersRegistry;
import org.apache.wink.server.internal.DeploymentConfiguration;
import org.apache.wink.server.internal.RequestProcessor;
import org.apache.wink.server.internal.registry.providers.Provider1;
import org.apache.wink.server.internal.registry.providers.Provider2;
import org.apache.wink.server.internal.registry.providers.Provider3;
import org.apache.wink.server.internal.registry.providers.Provider4;
import org.apache.wink.server.internal.servlet.MockServletInvocationTest;
import org.apache.wink.server.internal.servlet.RestServlet;
import org.apache.wink.test.mock.MockRequestConstructor;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
public class ProvidersRegistrySystemTest extends MockServletInvocationTest {
private static File winkprovidersFile = null;
private static File winkapplicationFile = null;
private static String rootPath = null;
private static final String backupExt = "_backup";
// store this instance so we can check that it was loaded from getSingletons after our Application has loaded
private static Provider1 provider1Singleton = new Provider1();
@Override
protected void setUp() throws Exception {
Field defaultFileField = ApplicationFileLoader.class.getDeclaredField("CORE_APPLICATION");
defaultFileField.setAccessible(true);
String filePath = (String)defaultFileField.get(null);
if (!filePath.startsWith("/")) {
filePath = "/" + filePath;
}
// use the path where CORE_APPLICATION is found as the root for the WINK_APPLICATION file
String classPath = System.getProperty("java.class.path");
StringTokenizer tokenizer = new StringTokenizer(classPath, System.getProperty("path.separator"));
while (tokenizer.hasMoreElements()) {
String temp = tokenizer.nextToken();
if (temp.contains("test-classes")) {
rootPath = temp;
break;
}
}
// save a backup of winkprovidersFile if it exists:
copyfile(rootPath + filePath, rootPath + filePath + backupExt);
// create a custom wink-providers file with known entries for this test
ArrayList<String> lines = new ArrayList<String>();
lines.add(Provider1.class.getName()); // should be ignored due to it already being listed in getSingletons
lines.add(Provider2.class.getName()); // should be ignored due to it already being listed in getClasses
lines.add(Provider3.class.getName()); // accepted
lines.add(StringProvider.class.getName()); // accepted, need it to complete end to end test
winkprovidersFile = createFile(rootPath + filePath, lines.toArray(new String[0]));
defaultFileField = ApplicationFileLoader.class.getDeclaredField("WINK_APPLICATION");
defaultFileField.setAccessible(true);
filePath = (String)defaultFileField.get(null);
if (!filePath.startsWith("/")) {
filePath = "/" + filePath;
}
// create a custom wink-application file with known entries for this test
lines = new ArrayList<String>();
lines.add(Provider1.class.getName()); // should be ignored due to it already being listed in getSingletons
lines.add(Provider2.class.getName()); // should be ignored due to it already being listed in getClasses
lines.add(Provider3.class.getName()); // should be ignored due to it already being listed in wink-providers
lines.add(Provider4.class.getName()); // accepted
winkapplicationFile = createFile(rootPath + filePath, lines.toArray(new String[0]));
super.setUp();
}
@Override
protected void tearDown() throws Exception {
if (!winkapplicationFile.delete()) {
fail("failed to delete file " + winkapplicationFile.getPath());
}
if (!winkprovidersFile.delete()) {
fail("failed to delete file " + winkprovidersFile.getPath());
}
// restore the backup:
copyfile(winkprovidersFile.getPath() + backupExt, winkprovidersFile.getPath());
super.tearDown();
}
@Override
protected String getApplicationClassName() {
return MyApplication.class.getName();
}
public static class MyApplication extends Application {
@Override
public Set<Class<?>> getClasses() {
HashSet<Class<?>> set = new LinkedHashSet<Class<?>>();
set.add(Provider1.class); // Provider1 should be ignored due to it already being listed in getSingletons
set.add(Provider2.class); // accepted
return set;
}
@Override
public Set<Object> getSingletons() {
HashSet<Object> set = new LinkedHashSet<Object>();
set.add(provider1Singleton);
set.add(new Provider1()); // should be ignored due to provider1Singleton already being listed
set.add(new ECHOResource());
return set;
}
}
@Path("/test")
public static class ECHOResource {
@POST
@Consumes(MediaType.TEXT_PLAIN)
@Produces(MediaType.TEXT_PLAIN)
@Path("priority")
public String echoString(String string) {
return string;
}
}
/**
* Test the order of loading and the prioritization of Providers based on that loading order.
*
* NOTE: this test depends on the MockServletInvocationTest continuing to use LinkedHashSet in its
* getClasses and getSingletons methods
*
*/
public void testProviderPrioritization() throws Exception {
// make sure all the lists were read and processed by tracking the number of hits to the ctors
Provider1 p1 = new Provider1();
assertEquals(5, p1.getNumCtorHits());
Provider2 p2 = new Provider2();
assertEquals(3, p2.getNumCtorHits());
Provider3 p3 = new Provider3();
assertEquals(2, p3.getNumCtorHits());
Provider4 p4 = new Provider4();
assertEquals(2, p4.getNumCtorHits());
// to actually inspect the list in the 'data' object in the MessageBodyReaders in the ProvidersRegistry would take a lot of
// reflection and hacking. We'll at least confirm that only 5 (due to AssetProvider) are in the ProvidersRegistry.
RestServlet servlet = (RestServlet)this.getServlet();
ServletContext context = servlet.getServletContext();
RequestProcessor processor = (RequestProcessor) context.getAttribute(RequestProcessor.class.getName());
DeploymentConfiguration config = processor.getConfiguration();
ProvidersRegistry providersRegistry = config.getProvidersRegistry();
// to confirm that the ignores are indeed happening, I need to get the private field
// "messageBodyReaders" object, then it's superclass "data" object and inspect it:
Field field = providersRegistry.getClass().getDeclaredField("messageBodyReaders");
field.setAccessible(true);
Object messageBodyReaders = field.get(providersRegistry);
Field field2 = messageBodyReaders.getClass().getSuperclass().getDeclaredField("data");
field2.setAccessible(true);
HashMap data = (HashMap)field2.get(messageBodyReaders);
HashSet readers = (HashSet)data.get(MediaType.WILDCARD_TYPE);
assertEquals(6, readers.size());
// under the covers, the "list" is a treeset, so the iterator does not really tell us any info about the sort
Set<String> expectedSet = new HashSet<String>(6);
expectedSet.add("Priority: 0.500000, ObjectFactory: SingletonOF: " + org.apache.wink.server.internal.registry.providers.Provider1.class.getName());
expectedSet.add("Priority: 0.100000, ObjectFactory: ClassMetadataPrototypeOF Class: " + org.apache.wink.common.internal.providers.entity.AssetProvider.class.getName());
expectedSet.add("Priority: 0.100000, ObjectFactory: SingletonOF: " + org.apache.wink.server.internal.registry.providers.Provider3.class.getName());
expectedSet.add("Priority: 0.500000, ObjectFactory: SingletonOF: " + org.apache.wink.server.internal.registry.providers.Provider2.class.getName());
expectedSet.add("Priority: 0.100000, ObjectFactory: SingletonOF: " + org.apache.wink.server.internal.registry.providers.Provider4.class.getName());
expectedSet.add("Priority: 0.100000, ObjectFactory: SingletonOF: " + StringProvider.class.getName());
// this is obviously not the best way to check this. If toString() output is changed, or the TreeSet implementation is modified,
// this test code will also have to be modified. Also, can't check order of the readers HashSet. But we're compatible with other
// locales now.
int count = 0;
for (Iterator it = readers.iterator(); it.hasNext();) {
Object obj = it.next();
assertTrue(obj.toString(), expectedSet.contains(obj.toString()));
count++;
}
// do a real transaction too to confirm that the listing in getClasses took the top spot
MockHttpServletRequest request =
MockRequestConstructor.constructMockRequest("POST",
"/test/priority",
MediaType.TEXT_PLAIN,
MediaType.TEXT_PLAIN,
"".getBytes());
MockHttpServletResponse response = invoke(request);
assertEquals(200, response.getStatus());
// make sure the first instance processed has top priority, and that it is indeed the first instance of Provider2 (because it was given
// higher priority due to getClasses being processed after getSingletons)
assertEquals(Provider2.class.getName() + "_" + p2.getFirstInstanceHashCode(), response.getContentAsString());
}
// utility method
private File createFile(String filePath, String[] lines) throws Exception {
File propFile = new File(filePath);
if (!propFile.exists()) {
File dir = new File(filePath.substring(0, filePath.lastIndexOf("/")));
dir.mkdirs();
propFile.createNewFile();
}
FileWriter fileWriter = new FileWriter(propFile);
for (int i = 0; i < lines.length; i++) {
fileWriter.write(lines[i]);
fileWriter.write(System.getProperty("line.separator"));
}
fileWriter.flush();
fileWriter.close();
return propFile;
}
private static void copyfile(String sourceFile, String destinationFile) throws Exception {
try {
File f1 = new File(sourceFile);
File f2 = new File(destinationFile);
InputStream in = new FileInputStream(f1);
OutputStream out = new FileOutputStream(f2);
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0){
out.write(buf, 0, len);
}
in.close();
out.close();
} catch (Exception e) {
// ignore
}
}
}