/*
* Copyright 2013
*
* Licensed 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.openntf.domino.design.impl;
import static javax.xml.bind.DatatypeConverter.parseBase64Binary;
import static javax.xml.bind.DatatypeConverter.printBase64Binary;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Logger;
import org.openntf.domino.Database;
import org.openntf.domino.Document;
import org.openntf.domino.design.XspXmlContent;
import org.openntf.domino.nsfdata.structs.cd.CData;
import org.openntf.domino.nsfdata.structs.obj.CDObject;
import org.openntf.domino.nsfdata.structs.obj.CDResourceEvent;
import org.openntf.domino.nsfdata.structs.obj.CDResourceFile;
import org.openntf.domino.utils.DominoUtils;
import org.openntf.domino.utils.xml.XMLDocument;
import org.openntf.domino.utils.xml.XMLNode;
public abstract class AbstractDesignFileResource extends AbstractDesignBaseNamed implements org.openntf.domino.design.AnyFileResource {
private static final long serialVersionUID = 1L;
@SuppressWarnings("unused")
private static final Logger log_ = Logger.getLogger(AbstractDesignFileResource.class.getName());
/* for web apps, this file is ready for primetime */
private static final char DESIGN_FLAGEXT_FILE_DEPLOYABLE = 'D';
private static final char DESIGN_FLAG_READONLY = '&';
private static final String DEFAULT_FILEDATA_FIELD = "$FileData";
private static final String MIMETYPE_FIELD = "$MimeType";
protected AbstractDesignFileResource(final Document document) {
super(document);
}
protected AbstractDesignFileResource(final Database database) {
super(database);
// TODO What is this?
try {
InputStream is = DesignView.class.getResourceAsStream("/org/openntf/domino/design/impl/dxl_fileresource.xml");
loadDxl(is);
is.close();
} catch (IOException e) {
DominoUtils.handleException(e);
}
}
protected AbstractDesignFileResource(final Database database, final String dxlResource) {
super(database);
try {
InputStream is = DesignView.class.getResourceAsStream(dxlResource);
loadDxl(is);
is.close();
} catch (IOException e) {
DominoUtils.handleException(e);
}
}
/*
* (non-Javadoc)
*
* @see org.openntf.domino.design.FileResource#getFileData(java.lang.String)
*/
@Override
public byte[] getFileData() {
switch (getDxlFormat(true)) {
case DXL:
String rawData = getDxl().selectSingleNode("//filedata").getText();
return parseBase64Binary(rawData);
default:
return getFileDataRaw(DEFAULT_FILEDATA_FIELD);
}
}
/**
* Reads a FileData Item in RAW-mode
*
* @param itemName
* @return
*/
protected byte[] getFileDataRaw(final String itemName) {
try {
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
for (XMLNode rawitemdata : getDxl().selectNodes(//
"//item[@name='" + XMLDocument.escapeXPathValue(itemName) + "']/rawitemdata")) {
String rawData = rawitemdata.getText();
byte[] thisData = parseBase64Binary(rawData);
byteStream.write(thisData);
}
if (byteStream.size() > 0) {
byte[] data = byteStream.toByteArray();
CData cdata = new CData(data);
CDObject obj = CDObject.create(cdata);
// Files may be attached either as FILE or as EVENT... (ssjs for example) Damn. This makes everything quite complex
if (obj instanceof CDResourceFile)
return ((CDResourceFile) obj).getFileData();
if (obj instanceof CDResourceEvent)
return ((CDResourceEvent) obj).getFileData();
throw new IllegalStateException("Cannot decode " + obj.getClass().getName());
} else {
byteStream = new ByteArrayOutputStream();
for (XMLNode rawitemdata : getDxl().selectNodes(//
"//file[@name='" + XMLDocument.escapeXPathValue(itemName) + "']/filedata")) {
String rawData = rawitemdata.getText();
byte[] thisData = parseBase64Binary(rawData);
byteStream.write(thisData);
}
return byteStream.toByteArray();
}
} catch (IOException ioe) {
DominoUtils.handleException(ioe);
return null;
}
}
@Override
public void setFileData(final byte[] data) {
switch (getDxlFormat(true)) {
case DXL:
XMLNode filedata = getDxl().selectSingleNode("//filedata");
if (filedata == null) {
filedata = getDxl().selectSingleNode("/*").addChildElement("filedata");
}
filedata.setText(printBase64Binary(data));
default:
setFileDataRaw(DEFAULT_FILEDATA_FIELD, data);
}
}
protected void setFileDataRaw(final String itemName, final byte[] fileData) {
// To set the file content, first clear out existing content
List<XMLNode> fileDataNodes = getDxl().selectNodes("//item[@name='" + XMLDocument.escapeXPathValue(itemName) + "']");
for (int i = fileDataNodes.size() - 1; i >= 0; i--) {
fileDataNodes.get(i).getParentNode().removeChild(fileDataNodes.get(i));
}
// Now create a CD record for the file data
// CDResourceFile record = CDResourceFile.fromFileData(fileData, "");
// byte[] reconData = record.getBytes();
CDResourceFile record = new CDResourceFile("");
record.setFileData(fileData);
byte[] reconData = record.getData().array();
// Write out the first chunk
int firstChunk = reconData.length > 20544 ? 20544 : reconData.length;
String firstChunkData = printBase64Binary(Arrays.copyOfRange(reconData, 0, firstChunk));
XMLNode documentNode = getDxl().selectSingleNode("//note");
XMLNode fileDataNode = documentNode.addChildElement("item");
fileDataNode.setAttribute("name", itemName);
fileDataNode = fileDataNode.addChildElement("rawitemdata");
fileDataNode.setAttribute("type", "1");
fileDataNode.setText(firstChunkData);
// Write out any remaining chunks
int remaining = reconData.length - firstChunk;
int chunks = remaining / 20516;
if (remaining % 20516 > 0) {
chunks++;
}
int offset = firstChunk;
for (int i = 0; i < chunks; i++) {
int chunkSize = remaining > 20516 ? 20516 : remaining;
String chunkData = printBase64Binary(Arrays.copyOfRange(reconData, offset, offset + chunkSize));
fileDataNode = documentNode.addChildElement("item");
fileDataNode.setAttribute("name", itemName);
fileDataNode = fileDataNode.addChildElement("rawitemdata");
fileDataNode.setAttribute("type", "1");
fileDataNode.setText(chunkData);
remaining -= 20516;
offset += chunkSize;
}
// Also set the file size if we're setting the main field
if (DEFAULT_FILEDATA_FIELD.equals(itemName)) {
setItemValue("$FileSize", String.valueOf(fileData.length), FLAG_SIGN_SUMMARY);
}
}
@Override
public String getMimeType() {
switch (getDxlFormat(false)) {
case DXL:
return getDocumentElement().getAttribute("mimetype");
default:
return getItemValueString(MIMETYPE_FIELD);
}
}
@Override
public void setMimeType(final String mimeType) {
switch (getDxlFormat(true)) {
case DXL:
getDocumentElement().setAttribute("mimetype", mimeType);
default:
setItemValue(MIMETYPE_FIELD, mimeType, FLAG_SUMMARY);
}
}
@Override
public void setName(final String title) {
super.setName(title);
// Also set the $FileNames field
setItemValue("$FileNames", title, FLAG_SIGN_SUMMARY);
}
/**
* The ODS file that is written here is the file content
*/
@Override
public void writeOnDiskFile(final File odpFile) throws IOException {
FileOutputStream fo = new FileOutputStream(odpFile);
fo.write(getFileData());
fo.close();
odpFile.setLastModified(getDocLastModified().getTime());
}
// TODO: map this to DXL
@Override
public boolean isReadOnly() {
return hasFlag(DESIGN_FLAG_READONLY);
}
@Override
public void setReadOnly(final boolean readOnly) {
setFlag(DESIGN_FLAG_READONLY, readOnly);
}
@Override
public boolean isDeployable() {
return hasFlagExt(DESIGN_FLAGEXT_FILE_DEPLOYABLE);
}
@Override
public void setDeployable(final boolean deployable) {
}
public <T extends XspXmlContent> T getAsXml(final Class<T> schema) {
try {
Constructor<T> ctor = schema.getConstructor(FileResource.class);
return ctor.newInstance(this);
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
}