如何正确添加和删除 MIME 附件
How to Add and Remove MIME attachments correctly
我正在向这样的文档添加 MIME 附件
try{
var d = database.getView("Main").getFirstDocument()
var it = d.getFirstItem("Body")
var att:NotesEmbeddedObject = it.getEmbeddedObject("mydoc.docx")
var streamDOC:NotesStream = session.createStream()
streamDOC.setContents(att.getInputStream())
var newd;
newd = database.getView("NewD").getFirstDocument()
if(newd==null){
newd = database.createDocument()
newd.replaceItemValue("Form","Main")
var me = newd.createMIMEEntity("Body")
}else{
var me = newd.getMIMEEntity("Body")
}
var filename = "test.pdf"
var mc = me.createChildEntity();
var he = mc.createHeader("Content-Disposition")
he.setHeaderVal("attachment; filename=\"" + filename + "\"");
he = mc.createHeader("Content-ID");
he.setHeaderVal( "<" + filename + ">" );
mc.setContentFromBytes(streamDOC, "application/vnd.openxmlformats-officedocument.wordprocessingml.document", NotesMIMEEntity.ENC_IDENTITY_8BIT);
newd.save()
print("success")
}catch(e){
print("fail " + e)
}
我重复提供了一个删除按钮
var eo = nd.getDocument().getAttachment(att)
eo.remove()
nd.save()
附件已从文档中删除,在 Ytria 中我可以看到 $FILE 项已删除但 BODY 项未删除。问题在于,如果我向同一文档添加新附件,我之前删除的所有附件都会回来
这是删除附件前文档的样子。
这里的文件大小很遗憾是0Kb,因为我用错了截图。从一开始所有 $File 项的大小都是正确的
这是我删除附件后文档的样子(使用上面的脚本)
这是我删除附件后添加一个附件(使用上面的脚本)后文档的样子
- 我添加或删除附件时会不会做错了什么? (看
脚本)
- Body字段是否有“store”似乎并不重要
内容作为 MIME”选项是否设置
- 另见这个问题
How to Add and Remove attachments using MIME
如果您使用 MIME 方法附加文件,为什么不使用 MIME 方法也将其删除?
我使用自己的框架,因此以下代码可能会给您留下过于复杂的印象,但希望您能理解它的要点:
我有一个枚举可以帮助我浏览各种 MIME 类型。在这种情况下,您正在处理 ATTACHMENT
:
public enum MimeContentType {
ATTACHMENT("attachment") {
@Override
public boolean matches(String[] headers) {
int score = 0;
for (String header : headers) {
if (header.startsWith("Content-Disposition")) {
score++;
}
if (header.contains("attachment")) {
score++;
}
if (header.contains("filename")) {
score++;
}
if (score == 3) {
return true;
}
}
return false;
}
},
TEXT("text"),
TEXT_HTML("text/html"),
TEXT_PLAIN("text/plain");
private final String type;
private MimeContentType(String type) {
this.type = type;
}
public boolean matches(String[] headers) {
for (String header : headers) {
if (header.startsWith("Content-Type") && header.contains(type)) {
return true;
}
}
return false;
}
}
然后是一些帮手类:
@FunctionalInterface
public interface ThrowableConsumer<T> extends Consumer<T> {
@Override
default void accept(final T t) {
try {
acceptOrThrow(t);
} catch (final Throwable e) {
throw new RuntimeException(e);
}
}
void acceptOrThrow(T t) throws Throwable;
}
@FunctionalInterface
public interface ThrowableFunction<T, R> extends Function<T, R> {
@Override
default R apply(T t) {
try {
return applyOrThrow(t);
} catch (final Throwable e) {
throw new RuntimeException(e);
}
}
R applyOrThrow(T t) throws Throwable;
}
@FunctionalInterface
public interface ThrowablePredicate<T> extends Predicate<T> {
@Override
default boolean test(T t) {
try {
return testOrThrow(t);
} catch (final Throwable e) {
throw new RuntimeException(e);
}
}
boolean testOrThrow(T t) throws Throwable;
}
@FunctionalInterface
public interface ThrowableSupplier<T> extends Supplier<T> {
@Override
default T get() {
try {
return getOrThrow();
} catch (final Throwable e) {
throw new RuntimeException(e);
}
}
T getOrThrow() throws Throwable;
}
public enum DominoUtil {
;
private static final Vector<String> MIME_FILTERED_HEADERS = new Vector<>();
static {
MIME_FILTERED_HEADERS.add("Content-Type");
MIME_FILTERED_HEADERS.add("Content-Disposition");
}
public static List<MIMEEntity> getMimeEntitiesByContentType(MIMEEntity entity,
MimeContentType contentType) throws NotesException {
Objects.requireNonNull(entity, "Entity cannot be null");
Objects.requireNonNull(contentType, "Content type cannot be null");
List<MIMEEntity> subentities = new ArrayList<>();
MIMEEntity nextEntity = null;
try {
nextEntity = entity.getNextEntity();
while (nextEntity != null) {
String[] entityFilteredHeaders = nextEntity
.getSomeHeaders(MIME_FILTERED_HEADERS, true)
.split("\n");
if (contentType.matches(entityFilteredHeaders)) {
subentities.add(nextEntity);
}
nextEntity = nextEntity.getNextEntity();
}
} finally {
DominoUtil.recycle(nextEntity);
}
return subentities;
}
public final static MIMEEntity getMimeEntity(Document doc, String itemName,
boolean createOnFail) throws NotesException {
if (itemName == null) {
throw new NullPointerException("Invalid MIME entity item name");
}
MIMEEntity mimeEntity = doc.getMIMEEntity(itemName);
if (mimeEntity == null) {
if (doc.hasItem(itemName)) {
doc.removeItem(itemName);
}
if (createOnFail) {
mimeEntity = doc.createMIMEEntity(itemName);
}
}
return mimeEntity;
}
public static Optional<String> getMimeEntityAttachmentFilename(MIMEEntity entity) throws NotesException {
Objects.requireNonNull(entity, "Entity cannot be null");
return getMimeEntityHeaderValAndParams(
entity, (ThrowablePredicate<MIMEHeader>) h -> h.getHeaderVal().equals("attachment"))
.map(s -> {
Matcher m = Pattern.compile("filename=['\"]?([^'\"\s]+)").matcher(s);
m.find();
return m.group(1);
});
}
public static Optional<String> getMimeEntityHeaderValAndParams(
MIMEEntity entity, Predicate<MIMEHeader> matcher) throws NotesException {
Objects.requireNonNull(entity, "Entity cannot be null");
Objects.requireNonNull(matcher, "Matcher cannot be null");
Vector<?> headers = entity.getHeaderObjects();
try {
return headers
.stream()
.map(MIMEHeader.class::cast)
.filter(matcher)
.map((ThrowableFunction<MIMEHeader, String>) MIMEHeader::getHeaderValAndParams)
.findFirst();
} finally {
recycle(headers);
}
}
public static void recycle(Base... bases) {
for (Base base : bases) {
if (base != null) {
try {
base.recycle();
} catch (Exception e) {
// Do nothing
}
}
}
}
public static void recycle(Collection<? extends Object> objs) {
objs.stream()
.filter(o -> o instanceof Base)
.map(o -> (Base) o)
.forEach(DominoUtil::recycle);
}
}
最后是完成这项工作的方法:
public class Example {
public static void yourEntryPoint() {
try {
// The last param is just a way to create an attachment from text
// You have InputStream to pass along obviously
addAttachment(doc, "Body", "fake1.txt", "this is fake text1");
addAttachment(doc, "Body", "fake2.txt", "this is fake text2");
addAttachment(doc, "Body", "fake3.txt", "this is fake text3");
removeAttachment(doc, "Body", "fake2.txt");
removeAttachment(doc, "Body", "fake3.txt");
} catch (NotesException e) {
throw new RuntimeException(e);
}
}
private static void addAttachment(Document doc, String itemName, String fileName, String data)
throws NotesException {
MIMEEntity mimeEntity = null;
Stream stm = null;
try {
mimeEntity = DominoUtil.getMimeEntity(doc, itemName, true);
Optional<MIMEEntity> optAttEntity = getAttachmentMimeEntity(mimeEntity, fileName);
MIMEEntity attachmentEntity = null;
if (optAttEntity.isPresent()) {
attachmentEntity = optAttEntity.get();
} else {
attachmentEntity = mimeEntity.createChildEntity();
MIMEHeader header = attachmentEntity.createHeader("Content-Disposition");
header.setHeaderValAndParams("attachment; filename=\"" + fileName + "\"");
}
stm = doc.getParentDatabase().getParent().createStream();
stm.writeText(data);
attachmentEntity.setContentFromBytes(stm,
"application/octet-stream",
MIMEEntity.ENC_IDENTITY_BINARY);
stm.close();
doc.closeMIMEEntities(true, itemName);
} finally {
DominoUtil.recycle(stm);
DominoUtil.recycle(mimeEntity);
}
}
private static void removeAttachment(Document doc, String itemName, String fileName)
throws NotesException {
MIMEEntity mimeEntity = null;
try {
// Get MIME entity
mimeEntity = DominoUtil.getMimeEntity(doc, itemName, true);
Optional<MIMEEntity> optAttEntity = getAttachmentMimeEntity(mimeEntity, fileName);
if (!optAttEntity.isPresent()) {
return;
}
optAttEntity.get().remove();
// Header cleaning on empty entity
if (mimeEntity.getFirstChildEntity() != null) {
doc.closeMIMEEntities(true, itemName);
} else {
mimeEntity.remove();
}
} finally {
DominoUtil.recycle(mimeEntity);
}
}
private static Optional<MIMEEntity> getAttachmentMimeEntity(MIMEEntity root, String fileName)
throws NotesException {
return DominoUtil
.getMimeEntitiesByContentType(root, MimeContentType.ATTACHMENT)
.stream()
.filter((ThrowablePredicate<MIMEEntity>) mime -> {
Optional<String> opt = DominoUtil.getMimeEntityAttachmentFilename(mime);
return opt.isPresent() && opt.get().equals(fileName);
})
.findFirst();
}
}
我正在向这样的文档添加 MIME 附件
try{
var d = database.getView("Main").getFirstDocument()
var it = d.getFirstItem("Body")
var att:NotesEmbeddedObject = it.getEmbeddedObject("mydoc.docx")
var streamDOC:NotesStream = session.createStream()
streamDOC.setContents(att.getInputStream())
var newd;
newd = database.getView("NewD").getFirstDocument()
if(newd==null){
newd = database.createDocument()
newd.replaceItemValue("Form","Main")
var me = newd.createMIMEEntity("Body")
}else{
var me = newd.getMIMEEntity("Body")
}
var filename = "test.pdf"
var mc = me.createChildEntity();
var he = mc.createHeader("Content-Disposition")
he.setHeaderVal("attachment; filename=\"" + filename + "\"");
he = mc.createHeader("Content-ID");
he.setHeaderVal( "<" + filename + ">" );
mc.setContentFromBytes(streamDOC, "application/vnd.openxmlformats-officedocument.wordprocessingml.document", NotesMIMEEntity.ENC_IDENTITY_8BIT);
newd.save()
print("success")
}catch(e){
print("fail " + e)
}
我重复提供了一个删除按钮
var eo = nd.getDocument().getAttachment(att)
eo.remove()
nd.save()
附件已从文档中删除,在 Ytria 中我可以看到 $FILE 项已删除但 BODY 项未删除。问题在于,如果我向同一文档添加新附件,我之前删除的所有附件都会回来
这是删除附件前文档的样子。
这里的文件大小很遗憾是0Kb,因为我用错了截图。从一开始所有 $File 项的大小都是正确的
这是我删除附件后文档的样子(使用上面的脚本)
这是我删除附件后添加一个附件(使用上面的脚本)后文档的样子
- 我添加或删除附件时会不会做错了什么? (看 脚本)
- Body字段是否有“store”似乎并不重要 内容作为 MIME”选项是否设置
- 另见这个问题 How to Add and Remove attachments using MIME
如果您使用 MIME 方法附加文件,为什么不使用 MIME 方法也将其删除?
我使用自己的框架,因此以下代码可能会给您留下过于复杂的印象,但希望您能理解它的要点:
我有一个枚举可以帮助我浏览各种 MIME 类型。在这种情况下,您正在处理 ATTACHMENT
:
public enum MimeContentType {
ATTACHMENT("attachment") {
@Override
public boolean matches(String[] headers) {
int score = 0;
for (String header : headers) {
if (header.startsWith("Content-Disposition")) {
score++;
}
if (header.contains("attachment")) {
score++;
}
if (header.contains("filename")) {
score++;
}
if (score == 3) {
return true;
}
}
return false;
}
},
TEXT("text"),
TEXT_HTML("text/html"),
TEXT_PLAIN("text/plain");
private final String type;
private MimeContentType(String type) {
this.type = type;
}
public boolean matches(String[] headers) {
for (String header : headers) {
if (header.startsWith("Content-Type") && header.contains(type)) {
return true;
}
}
return false;
}
}
然后是一些帮手类:
@FunctionalInterface
public interface ThrowableConsumer<T> extends Consumer<T> {
@Override
default void accept(final T t) {
try {
acceptOrThrow(t);
} catch (final Throwable e) {
throw new RuntimeException(e);
}
}
void acceptOrThrow(T t) throws Throwable;
}
@FunctionalInterface
public interface ThrowableFunction<T, R> extends Function<T, R> {
@Override
default R apply(T t) {
try {
return applyOrThrow(t);
} catch (final Throwable e) {
throw new RuntimeException(e);
}
}
R applyOrThrow(T t) throws Throwable;
}
@FunctionalInterface
public interface ThrowablePredicate<T> extends Predicate<T> {
@Override
default boolean test(T t) {
try {
return testOrThrow(t);
} catch (final Throwable e) {
throw new RuntimeException(e);
}
}
boolean testOrThrow(T t) throws Throwable;
}
@FunctionalInterface
public interface ThrowableSupplier<T> extends Supplier<T> {
@Override
default T get() {
try {
return getOrThrow();
} catch (final Throwable e) {
throw new RuntimeException(e);
}
}
T getOrThrow() throws Throwable;
}
public enum DominoUtil {
;
private static final Vector<String> MIME_FILTERED_HEADERS = new Vector<>();
static {
MIME_FILTERED_HEADERS.add("Content-Type");
MIME_FILTERED_HEADERS.add("Content-Disposition");
}
public static List<MIMEEntity> getMimeEntitiesByContentType(MIMEEntity entity,
MimeContentType contentType) throws NotesException {
Objects.requireNonNull(entity, "Entity cannot be null");
Objects.requireNonNull(contentType, "Content type cannot be null");
List<MIMEEntity> subentities = new ArrayList<>();
MIMEEntity nextEntity = null;
try {
nextEntity = entity.getNextEntity();
while (nextEntity != null) {
String[] entityFilteredHeaders = nextEntity
.getSomeHeaders(MIME_FILTERED_HEADERS, true)
.split("\n");
if (contentType.matches(entityFilteredHeaders)) {
subentities.add(nextEntity);
}
nextEntity = nextEntity.getNextEntity();
}
} finally {
DominoUtil.recycle(nextEntity);
}
return subentities;
}
public final static MIMEEntity getMimeEntity(Document doc, String itemName,
boolean createOnFail) throws NotesException {
if (itemName == null) {
throw new NullPointerException("Invalid MIME entity item name");
}
MIMEEntity mimeEntity = doc.getMIMEEntity(itemName);
if (mimeEntity == null) {
if (doc.hasItem(itemName)) {
doc.removeItem(itemName);
}
if (createOnFail) {
mimeEntity = doc.createMIMEEntity(itemName);
}
}
return mimeEntity;
}
public static Optional<String> getMimeEntityAttachmentFilename(MIMEEntity entity) throws NotesException {
Objects.requireNonNull(entity, "Entity cannot be null");
return getMimeEntityHeaderValAndParams(
entity, (ThrowablePredicate<MIMEHeader>) h -> h.getHeaderVal().equals("attachment"))
.map(s -> {
Matcher m = Pattern.compile("filename=['\"]?([^'\"\s]+)").matcher(s);
m.find();
return m.group(1);
});
}
public static Optional<String> getMimeEntityHeaderValAndParams(
MIMEEntity entity, Predicate<MIMEHeader> matcher) throws NotesException {
Objects.requireNonNull(entity, "Entity cannot be null");
Objects.requireNonNull(matcher, "Matcher cannot be null");
Vector<?> headers = entity.getHeaderObjects();
try {
return headers
.stream()
.map(MIMEHeader.class::cast)
.filter(matcher)
.map((ThrowableFunction<MIMEHeader, String>) MIMEHeader::getHeaderValAndParams)
.findFirst();
} finally {
recycle(headers);
}
}
public static void recycle(Base... bases) {
for (Base base : bases) {
if (base != null) {
try {
base.recycle();
} catch (Exception e) {
// Do nothing
}
}
}
}
public static void recycle(Collection<? extends Object> objs) {
objs.stream()
.filter(o -> o instanceof Base)
.map(o -> (Base) o)
.forEach(DominoUtil::recycle);
}
}
最后是完成这项工作的方法:
public class Example {
public static void yourEntryPoint() {
try {
// The last param is just a way to create an attachment from text
// You have InputStream to pass along obviously
addAttachment(doc, "Body", "fake1.txt", "this is fake text1");
addAttachment(doc, "Body", "fake2.txt", "this is fake text2");
addAttachment(doc, "Body", "fake3.txt", "this is fake text3");
removeAttachment(doc, "Body", "fake2.txt");
removeAttachment(doc, "Body", "fake3.txt");
} catch (NotesException e) {
throw new RuntimeException(e);
}
}
private static void addAttachment(Document doc, String itemName, String fileName, String data)
throws NotesException {
MIMEEntity mimeEntity = null;
Stream stm = null;
try {
mimeEntity = DominoUtil.getMimeEntity(doc, itemName, true);
Optional<MIMEEntity> optAttEntity = getAttachmentMimeEntity(mimeEntity, fileName);
MIMEEntity attachmentEntity = null;
if (optAttEntity.isPresent()) {
attachmentEntity = optAttEntity.get();
} else {
attachmentEntity = mimeEntity.createChildEntity();
MIMEHeader header = attachmentEntity.createHeader("Content-Disposition");
header.setHeaderValAndParams("attachment; filename=\"" + fileName + "\"");
}
stm = doc.getParentDatabase().getParent().createStream();
stm.writeText(data);
attachmentEntity.setContentFromBytes(stm,
"application/octet-stream",
MIMEEntity.ENC_IDENTITY_BINARY);
stm.close();
doc.closeMIMEEntities(true, itemName);
} finally {
DominoUtil.recycle(stm);
DominoUtil.recycle(mimeEntity);
}
}
private static void removeAttachment(Document doc, String itemName, String fileName)
throws NotesException {
MIMEEntity mimeEntity = null;
try {
// Get MIME entity
mimeEntity = DominoUtil.getMimeEntity(doc, itemName, true);
Optional<MIMEEntity> optAttEntity = getAttachmentMimeEntity(mimeEntity, fileName);
if (!optAttEntity.isPresent()) {
return;
}
optAttEntity.get().remove();
// Header cleaning on empty entity
if (mimeEntity.getFirstChildEntity() != null) {
doc.closeMIMEEntities(true, itemName);
} else {
mimeEntity.remove();
}
} finally {
DominoUtil.recycle(mimeEntity);
}
}
private static Optional<MIMEEntity> getAttachmentMimeEntity(MIMEEntity root, String fileName)
throws NotesException {
return DominoUtil
.getMimeEntitiesByContentType(root, MimeContentType.ATTACHMENT)
.stream()
.filter((ThrowablePredicate<MIMEEntity>) mime -> {
Optional<String> opt = DominoUtil.getMimeEntityAttachmentFilename(mime);
return opt.isPresent() && opt.get().equals(fileName);
})
.findFirst();
}
}