/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2011-2012 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package org.glassfish.admin.payload;
import com.sun.enterprise.util.io.FileUtils;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.LineNumberReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.URI;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Logger;
import org.glassfish.api.admin.Payload;
import org.junit.After;
import org.junit.AfterClass;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
/**
*
* @author Tim Quinn
*/
public class PayloadImplTest {
private static class FileInfo {
private final String name;
private final String path;
private final String pathPrefix;
private final String content;
private FileInfo(final String name, final String pathPrefix, final String content) {
this.name = name;
this.pathPrefix = pathPrefix;
this.path = (pathPrefix == null ? "" : pathPrefix + '/') + name;
this.content = content;
}
private File file(final File rootDir) {
return new File(rootDir.toURI().resolve(path));
}
}
private static final String SUBDIR_LEVEL_1 = "a";
private static final String SUBDIR_LEVEL_2 = "a1";
private static final FileInfo ORIGINAL_FILE_X =
new FileInfo("x.txt", SUBDIR_LEVEL_1 + '/' + SUBDIR_LEVEL_2, "old x");
private static final FileInfo ORIGINAL_FILE_Y =
new FileInfo("y.txt", SUBDIR_LEVEL_1, "old y");
private static final FileInfo[] ORIGINAL_FILES = {
ORIGINAL_FILE_X, ORIGINAL_FILE_Y};
private static final FileInfo ADDED_FILE_Z =
new FileInfo("z.txt", SUBDIR_LEVEL_1 + '/' + SUBDIR_LEVEL_2,
"new z");
private static final FileInfo REPLACED_FILE_X =
new FileInfo(ORIGINAL_FILE_X.name, ORIGINAL_FILE_X.pathPrefix, "replaced x");
private static final String REPL_SUBDIR_LEVEL_1 = "repl-a";
private static final String REPL_SUBDIR_LEVEL_2 = "repl-a1";
private static final FileInfo REPLACEMENT_FILE_A =
new FileInfo("r-a.txt", REPL_SUBDIR_LEVEL_1, "replacement a");
private static final FileInfo REPLACEMENT_FILE_B =
new FileInfo("r-b.txt", null, "replacement b");
private static final FileInfo REPLACEMENT_FILE_C =
new FileInfo("r-c.txt", REPL_SUBDIR_LEVEL_1 + '/' + REPL_SUBDIR_LEVEL_2, "replacement c");
private static final FileInfo[] REPLACEMENT_FILES = {
REPLACEMENT_FILE_A, REPLACEMENT_FILE_B, REPLACEMENT_FILE_C};
/** The path of the original file that is replaced but the name and content of the replacement file */
private static final FileInfo REPLACED_FILE_C =
new FileInfo(REPLACEMENT_FILE_C.name, SUBDIR_LEVEL_1 + '/' + SUBDIR_LEVEL_2,
REPLACEMENT_FILE_C.content);
private static final String LINE_SEP = System.getProperty("line.separator");
private File workingDir = null;
private File replDir = null;
public PayloadImplTest() {
}
@BeforeClass
public static void setUpClass() throws Exception {
}
@AfterClass
public static void tearDownClass() throws Exception {
}
@Before
public void setUp() {
try {
workingDir = createAndPopulateWorkingDir();
log(" Working dir is set up at " + workingDir.getAbsolutePath());
replDir = createAndPopulateReplacementDir();
log(" Replacement dir is set up at " + replDir.getAbsolutePath());
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
@After
public void tearDown() {
cleanDir(workingDir);
log(" Working dir cleaned");
cleanDir(replDir);
log(" Replacement dir cleaned");
}
@Test
public void testDirectoryReplaceRequest() {
/*
* We try to replace the original subdir1/subdir2 in the working dir
* with some of the replacement content from the repl dir.
*/
System.out.println("testDirectoryReplaceRequest");
File dirToUseForReplacement = new File(replDir, REPL_SUBDIR_LEVEL_1);
dirToUseForReplacement = new File(dirToUseForReplacement, REPL_SUBDIR_LEVEL_2);
File zipFile = null;
try {
Payload.Outbound outboundPayload = PayloadImpl.Outbound.newInstance();
outboundPayload.requestFileReplacement("application/octet-stream",
new URI(SUBDIR_LEVEL_1 + '/' + SUBDIR_LEVEL_2 + '/'), "replacement",
null, dirToUseForReplacement, true);
zipFile = writePayloadToFile(outboundPayload, File.createTempFile("payloadZip", ".zip"));
preparePFM(zipFile);
/*
* Make sure the directory was replaced as desired and that other
* original files are still there. Specificaly, original file Y should still
* exist, but original file X should be gone. Replacement file C should
* now exist in the original directory but not other replacement files.
*/
checkFile(ORIGINAL_FILE_Y, ORIGINAL_FILE_Y.file(workingDir));
checkNoFile(ORIGINAL_FILE_X, ORIGINAL_FILE_X.file(workingDir));
checkFile(REPLACED_FILE_C, REPLACED_FILE_C.file(workingDir));
} catch (Exception ex) {
throw new RuntimeException(ex);
} finally {
deleteAndLogFailure(zipFile);
}
}
private void checkFile(final FileInfo fileInfo, final File f) throws FileNotFoundException, IOException {
assertTrue("Expected output file " + fileInfo.path + " does not exist",
f.exists());
assertEquals("Expected file contents for " + fileInfo.path + " not correct",
fileInfo.content, readFromFile(f));
}
private void checkNoFile(final FileInfo fileInfo, final File f) {
assertFalse("File " + fileInfo.path + " not expected but exists",
f.exists());
}
@Test
public void testSingleFileReplaceRequest() {
System.out.println("testSingleFileReplaceRequest");
File newVersion = null;
File zipFile = null;
try {
Payload.Outbound outboundPayload = PayloadImpl.Outbound.newInstance();
newVersion = populateFile(File.createTempFile("payload",".txt"), REPLACED_FILE_X.content);
outboundPayload.requestFileReplacement("application/octet-stream",
new URI(REPLACED_FILE_X.path), "replacement",
null, newVersion, false);
zipFile = writePayloadToFile(outboundPayload, File.createTempFile("payloadZip", ".zip"));
boolean isFileProcessed = false;
for (Map.Entry<File,Properties> entry : preparePFM(zipFile).entrySet()) {
final File processedFile = entry.getKey();
if (processedFile.toURI().getPath().endsWith(REPLACED_FILE_X.path)) {
isFileProcessed = true;
/*
* Make sure the file exists and contains the new content.
*/
assertTrue("Expected original output file " + REPLACED_FILE_X.path + " does not exist",
processedFile.exists());
assertEquals("Expected new file contents for " + REPLACED_FILE_X.path + " not correct",
REPLACED_FILE_X.content, readFromFile(processedFile));
}
}
} catch (Exception ex) {
throw new RuntimeException(ex);
} finally {
deleteAndLogFailure(newVersion);
deleteAndLogFailure(zipFile);
}
}
private static void deleteAndLogFailure(final File f) {
if (f != null) {
if ( ! f.delete()) {
System.err.println("Could not delete " + f.getAbsolutePath() + "; continuing");
}
}
}
@Test
public void testAddFiles() {
System.out.println("testAddFiles");
File fileToBeAddedToPayload = null;
File zipFile = null;
try {
Payload.Outbound outboundPayload = PayloadImpl.Outbound.newInstance();
fileToBeAddedToPayload = populateFile(File.createTempFile("payload",".txt"), ADDED_FILE_Z.content);
log(" Populated " + fileToBeAddedToPayload.getAbsolutePath());
/*
* Use application/octet-stream here. text/plain might seem more
* logical, but the payload implementation correctly treats a
* payload containing a single text entry differently from a
* multi-part payload or a payload containing a single non-text part.
* (This is because admin operations return their results in a text
* entry in the payload of the HTTP request. If no other data is
* being streamed then the requirement is to return regular text in
* the payload which makes non-asadmin clients - such as IDEs and
* web browsers - happier.)
*/
outboundPayload.attachFile("application/octet-stream", new URI(ADDED_FILE_Z.path), "addText",
fileToBeAddedToPayload);
log(" Attached " + ADDED_FILE_Z.path);
zipFile = writePayloadToFile(outboundPayload, File.createTempFile("payloadZip", ".zip"));
log(" Wrote payload to " + zipFile.getAbsolutePath());
// XXX consume the map; check for the new file and contents
boolean isFileProcessed = false;
for (Map.Entry<File,Properties> entry : preparePFM(zipFile).entrySet()) {
final File processedFile = entry.getKey();
if (processedFile.toURI().getPath().endsWith(ADDED_FILE_Z.path)) {
isFileProcessed = true;
/*
* Make sure the file exists and contains what we expect.
*/
assertTrue("Expected output file " + ADDED_FILE_Z.path + " does not exist",
processedFile.exists());
assertEquals("Expected new file contents for " + ADDED_FILE_Z.path + "not correct",
ADDED_FILE_Z.content, readFromFile(processedFile));
}
}
assertTrue("Added file " + ADDED_FILE_Z.path + " did not appear in the processed output",
isFileProcessed);
/*
* Make sure the original file still exists and contains what it should.
*/
final File originalFile = new File(workingDir.toURI().resolve(ORIGINAL_FILE_X.path));
assertTrue("Original file " + ORIGINAL_FILE_X.path + " no longer exists but it should",
originalFile.exists());
assertEquals("Original file " + ORIGINAL_FILE_X.path +
" exists as expected but no longer contains what it should",
ORIGINAL_FILE_X.content, readFromFile(originalFile));
} catch (Exception ex) {
throw new RuntimeException(ex);
} finally {
deleteAndLogFailure(fileToBeAddedToPayload);
deleteAndLogFailure(zipFile);
}
}
private Map<File,Properties> preparePFM(final File zipFile) throws FileNotFoundException, IOException, Exception {
PayloadFilesManager.Perm pfm = new PayloadFilesManager.Perm(
workingDir, null /* actionReporter */, Logger.getAnonymousLogger());
final InputStream is = new BufferedInputStream(new FileInputStream(zipFile));
Payload.Inbound inboundPayload = PayloadImpl.Inbound.newInstance("application/zip", is);
final Map<File,Properties> map = pfm.processPartsExtended(inboundPayload);
return map;
}
private File writePayloadToFile(final Payload.Outbound ob, final File f) throws FileNotFoundException, IOException {
final OutputStream os = new BufferedOutputStream(new FileOutputStream(f));
ob.writeTo(os);
return f;
}
private String readFromFile(final File f) throws FileNotFoundException, IOException {
final StringBuilder sb = new StringBuilder();
final LineNumberReader r = new LineNumberReader(new FileReader(f));
try {
String line;
while ((line = r.readLine()) != null) {
if (sb.length() > 0) {
sb.append(LINE_SEP);
}
sb.append(line);
}
return sb.toString();
} finally {
r.close();
}
}
/**
* Create a directory anchored at a temp directory that looks like this:
* a/
* a/a1/
* a/a1/x.txt (contains "old x")
*
* @return
*/
private File createAndPopulateWorkingDir() throws IOException {
return createAndPopulateDir(SUBDIR_LEVEL_1, SUBDIR_LEVEL_2, ORIGINAL_FILES);
}
private File createAndPopulateReplacementDir() throws IOException {
return createAndPopulateDir(REPL_SUBDIR_LEVEL_1,
REPL_SUBDIR_LEVEL_2, REPLACEMENT_FILES);
}
private File createAndPopulateDir(final String subdirLevel1Path, final String subdirLevel2Path,
final FileInfo[] files) throws IOException {
final File top = createTempDir("payload", "");
final File subdirLevel1 = createSubdir(top, subdirLevel1Path);
createSubdir(subdirLevel1, subdirLevel2Path);
for (FileInfo replFile : files) {
populateFile(new File(top.toURI().resolve(replFile.path)),
replFile.content);
}
return top;
}
private File createTempDir(final String prefix, final String suffix) throws IOException {
File temp = File.createTempFile(prefix, suffix);
if ( ! temp.delete()) {
throw new IOException("Cannot delete temp file " + temp.getAbsolutePath());
}
if ( ! temp.mkdirs()) {
throw new IOException("Cannot create temp directory" + temp.getAbsolutePath());
}
return temp;
}
private File createSubdir(final File parent, final String subdirPath) throws IOException {
final File f = new File(parent, subdirPath);
if ( ! f.mkdirs()) {
throw new IOException("Cannot create temp subdir " + f.getAbsolutePath());
}
return f;
}
private File populateFile(final File f, final String content) throws FileNotFoundException {
final PrintStream ps = new PrintStream(f);
ps.println(content);
ps.close();
return f;
}
private void cleanDir(final File d) {
FileUtils.whack(d);
}
private void log(final String s) {
//System.out.println(s);
}
}