/*
* JBoss, Home of Professional Open Source.
* Copyright 2013, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.as.patching.metadata;
import static org.jboss.as.controller.parsing.ParseUtils.readStringAttributeElement;
import static org.jboss.as.controller.parsing.ParseUtils.requireNoContent;
import static org.jboss.as.controller.parsing.ParseUtils.unexpectedAttribute;
import static org.jboss.as.controller.parsing.ParseUtils.unexpectedElement;
import static org.jboss.as.patching.HashUtils.bytesToHexString;
import static org.jboss.as.patching.HashUtils.hexStringToByteArray;
import static org.jboss.as.patching.IoUtils.NO_CONTENT;
import static org.jboss.as.patching.metadata.ModuleItem.MAIN_SLOT;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import org.jboss.as.controller.logging.ControllerLogger;
import org.jboss.as.patching.PatchingException;
import org.jboss.as.patching.installation.InstalledIdentity;
import org.jboss.as.patching.metadata.impl.PatchElementImpl;
import org.jboss.as.patching.metadata.impl.PatchElementProviderImpl;
import org.jboss.as.patching.metadata.impl.RequiresCallback;
import org.jboss.staxmapper.XMLExtendedStreamReader;
import org.jboss.staxmapper.XMLExtendedStreamWriter;
/**
* @author Emanuel Muckenhuber
*/
class PatchXmlUtils implements XMLStreamConstants {
private static final String PATH_DELIMITER = "/";
enum Element {
ADDED("added"),
BUNDLES("bundles"),
DESCRIPTION("description"),
IDENTITY("identity"),
REQUIRES("requires"),
LINK("link"),
MISC_FILES("misc-files"),
MODULES("modules"),
NO_UPGRADE("no-upgrade"),
ONE_OFF("one-off"),
PATCH("patch"),
PATCH_ELEMENT("element"),
REMOVED("removed"),
UPDATED("updated"),
UPGRADE("upgrade"),
// default unknown element
UNKNOWN(null),
;
public final String name;
Element(String name) {
this.name = name;
}
static Map<String, Element> elements = new HashMap<String, Element>();
static {
for(Element element : Element.values()) {
if(element != UNKNOWN) {
elements.put(element.name, element);
}
}
}
static Element forName(String name) {
final Element element = elements.get(name);
return element == null ? UNKNOWN : element;
}
}
enum Attribute {
ADD_ON("add-on"),
DIRECTORY("directory"),
EXISTING_PATH("existing-path"),
HASH("hash"),
ID("id"),
IN_RUNTIME_USE("in-runtime-use"),
NAME("name"),
NEW_HASH("new-hash"),
PATH("path"),
CONDITION("condition"),
SLOT("slot"),
TO_VERSION("to-version"),
URL("url"),
VERSION("version"),
// default unknown attribute
UNKNOWN(null);
private final String name;
Attribute(String name) {
this.name = name;
}
static Map<String, Attribute> attributes = new HashMap<String, Attribute>();
static {
for(Attribute attribute : Attribute.values()) {
if(attribute != UNKNOWN) {
attributes.put(attribute.name, attribute);
}
}
}
static Attribute forName(String name) {
final Attribute attribute = attributes.get(name);
return attribute == null ? UNKNOWN : attribute;
}
}
protected static void writePatch(final XMLExtendedStreamWriter writer, final Patch patch) throws XMLStreamException {
// id
writer.writeAttribute(Attribute.ID.name, patch.getPatchId());
// Description
String description = patch.getDescription();
if (description != null) {
writer.writeStartElement(Element.DESCRIPTION.name);
writer.writeCharacters(description);
writer.writeEndElement(); // description
}
String link = patch.getLink();
if (link != null) {
writer.writeEmptyElement(Element.LINK.name);
writer.writeAttribute(Attribute.URL.name, link);
}
// identity
final Identity identity = patch.getIdentity();
final Patch.PatchType type = identity.getPatchType();
if (type == Patch.PatchType.CUMULATIVE) {
writer.writeStartElement(Element.UPGRADE.name);
} else {
writer.writeStartElement(Element.NO_UPGRADE.name);
}
writer.writeAttribute(Attribute.NAME.name, identity.getName());
writer.writeAttribute(Attribute.VERSION.name, identity.getVersion());
// upgrade / no-upgrade
if(type == Patch.PatchType.CUMULATIVE) {
final Identity.IdentityUpgrade upgrade = identity.forType(Patch.PatchType.CUMULATIVE, Identity.IdentityUpgrade.class);
writer.writeAttribute(Attribute.TO_VERSION.name, upgrade.getResultingVersion());
}
if(!identity.getRequires().isEmpty()) {
writer.writeStartElement(Element.REQUIRES.name);
for(String patchId : identity.getRequires()) {
writer.writeStartElement(Element.PATCH.name);
writer.writeAttribute(Attribute.ID.name, patchId);
writer.writeEndElement(); // patch
}
writer.writeEndElement(); // includes
}
writer.writeEndElement(); // identity
// elements
final List<PatchElement> elements = patch.getElements();
for(PatchElement element : elements) {
writer.writeStartElement(Element.PATCH_ELEMENT.name);
writer.writeAttribute(Attribute.ID.name, element.getId());
if(element.getDescription() != null) {
writer.writeStartElement(Element.DESCRIPTION.name);
writer.writeCharacters(element.getDescription());
writer.writeEndElement(); // description
}
// layer / add-on
final PatchElementProvider provider = element.getProvider();
assert provider != null;
// identity
final Patch.PatchType elementPatchType = provider.getPatchType();
if (elementPatchType == Patch.PatchType.CUMULATIVE) {
writer.writeStartElement(Element.UPGRADE.name);
} else {
writer.writeStartElement(Element.NO_UPGRADE.name);
}
writer.writeAttribute(Attribute.NAME.name, provider.getName());
if (provider.isAddOn()) {
writer.writeAttribute(Attribute.ADD_ON.name, "true");
}
if(!provider.getRequires().isEmpty()) {
writer.writeStartElement(Element.REQUIRES.name);
for(String elementId : provider.getRequires()) {
writer.writeStartElement(Element.PATCH.name);
writer.writeAttribute(Attribute.ID.name, elementId);
writer.writeEndElement(); // element
}
writer.writeEndElement(); // includes
}
writer.writeEndElement(); // add-on / layer
// Write the content modifications
writeContentModifications(writer, element.getModifications());
writer.writeEndElement(); // element
}
// Write the identity modifications directory for some tests
writeContentModifications(writer, patch.getModifications());
}
protected static void writeContentModifications(final XMLExtendedStreamWriter writer, final Collection<ContentModification> modifications) throws XMLStreamException {
// Sort by content and modification type
final List<ContentModification> bundlesAdd = new ArrayList<ContentModification>();
final List<ContentModification> bundlesUpdate = new ArrayList<ContentModification>();
final List<ContentModification> bundlesRemove = new ArrayList<ContentModification>();
final List<ContentModification> miscAdd = new ArrayList<ContentModification>();
final List<ContentModification> miscUpdate = new ArrayList<ContentModification>();
final List<ContentModification> miscRemove = new ArrayList<ContentModification>();
final List<ContentModification> modulesAdd = new ArrayList<ContentModification>();
final List<ContentModification> modulesUpdate = new ArrayList<ContentModification>();
final List<ContentModification> modulesRemove = new ArrayList<ContentModification>();
for(final ContentModification mod : modifications) {
final ModificationType modificationType = mod.getType();
final ContentType contentType = mod.getItem().getContentType();
switch (contentType) {
case BUNDLE:
switch (modificationType) {
case ADD:
bundlesAdd.add(mod);
break;
case MODIFY:
bundlesUpdate.add(mod);
break;
case REMOVE:
bundlesRemove.add(mod);
break;
}
break;
case MODULE:
switch (modificationType) {
case ADD:
modulesAdd.add(mod);
break;
case MODIFY:
modulesUpdate.add(mod);
break;
case REMOVE:
modulesRemove.add(mod);
break;
}
break;
case MISC:
switch (modificationType) {
case ADD:
miscAdd.add(mod);
break;
case MODIFY:
miscUpdate.add(mod);
break;
case REMOVE:
miscRemove.add(mod);
break;
}
break;
}
}
// Modules
if (!modulesAdd.isEmpty() ||
!modulesUpdate.isEmpty() ||
!modulesRemove.isEmpty()) {
writer.writeStartElement(Element.MODULES.name);
writeSlottedItems(writer, Element.ADDED, modulesAdd);
writeSlottedItems(writer, Element.UPDATED, modulesUpdate);
writeSlottedItems(writer, Element.REMOVED, modulesRemove);
writer.writeEndElement(); // modules
}
// Bundles
if (!bundlesAdd.isEmpty() ||
!bundlesUpdate.isEmpty() ||
!bundlesRemove.isEmpty()) {
writer.writeStartElement(Element.BUNDLES.name);
writeSlottedItems(writer, Element.ADDED, bundlesAdd);
writeSlottedItems(writer, Element.UPDATED, bundlesUpdate);
writeSlottedItems(writer, Element.REMOVED, bundlesRemove);
writer.writeEndElement(); // bundles
}
// Misc
if (!miscAdd.isEmpty() ||
!miscUpdate.isEmpty() ||
!miscRemove.isEmpty()) {
writer.writeStartElement(Element.MISC_FILES.name);
writeMiscItems(writer, Element.ADDED, miscAdd);
writeMiscItems(writer, Element.UPDATED, miscUpdate);
writeMiscItems(writer, Element.REMOVED, miscRemove);
writer.writeEndElement(); // misc-files
}
}
protected void doReadElement(final XMLExtendedStreamReader reader, final PatchBuilder builder, InstalledIdentity originalIdentity) throws XMLStreamException {
final PatchBuilder patch = builder;
final int count = reader.getAttributeCount();
for (int i = 0; i < count; i++) {
final String value = reader.getAttributeValue(i);
final Attribute attribute = Attribute.forName(reader.getAttributeLocalName(i));
if(Attribute.ID == attribute) {
patch.setPatchId(value);
} else {
throw unexpectedAttribute(reader, i);
}
}
final Collection<ContentModification> modifications = patch.getModifications();
while (reader.hasNext() && reader.nextTag() != END_ELEMENT) {
final String localName = reader.getLocalName();
final Element element = Element.forName(localName);
switch (element) {
case DESCRIPTION:
patch.setDescription(reader.getElementText());
break;
case LINK:
final String link = readStringAttributeElement(reader, Attribute.URL.name);
builder.setLink(link);
break;
case UPGRADE:
parseIdentity(reader, patch, Patch.PatchType.CUMULATIVE);
break;
case NO_UPGRADE:
parseIdentity(reader, patch, Patch.PatchType.ONE_OFF);
break;
case PATCH_ELEMENT:
parseElement(reader, patch);
break;
case MODULES:
parseModules(reader, modifications);
break;
case BUNDLES:
parseBundles(reader, modifications);
break;
case MISC_FILES:
parseMiscFiles(reader, modifications);
break;
default:
handleRootElement(localName, reader, patch, originalIdentity);
}
}
}
protected void handleRootElement(final String localName, final XMLExtendedStreamReader reader, final PatchBuilder builder, InstalledIdentity originalIdentity) throws XMLStreamException {
throw unexpectedElement(reader);
}
static void parseElement(final XMLExtendedStreamReader reader, final PatchBuilder builder) throws XMLStreamException {
String id = null;
final int count = reader.getAttributeCount();
for (int i = 0; i < count; i++) {
final String value = reader.getAttributeValue(i);
final Attribute attribute = Attribute.forName(reader.getAttributeLocalName(i));
if (Attribute.ID == attribute) {
id = value;
} else {
throw unexpectedAttribute(reader, i);
}
}
final PatchElementImpl patchElement = new PatchElementImpl(id);
try {
builder.addElement(patchElement);
} catch(IllegalStateException e) {
throw new XMLStreamException(e);
}
final List<ContentModification> modifications = patchElement.getModifications();
while (reader.hasNext() && reader.nextTag() != END_ELEMENT) {
final Element element = Element.forName(reader.getLocalName());
switch (element) {
case DESCRIPTION:
patchElement.setDescription(reader.getElementText());
break;
case UPGRADE:
parseElementProvider(reader, patchElement, Patch.PatchType.CUMULATIVE);
break;
case NO_UPGRADE:
parseElementProvider(reader, patchElement, Patch.PatchType.ONE_OFF);
break;
case MODULES:
parseModules(reader, modifications);
break;
case BUNDLES:
parseBundles(reader, modifications);
break;
case MISC_FILES:
parseMiscFiles(reader, modifications);
break;
default:
throw unexpectedElement(reader);
}
}
}
static void parseElementProvider(final XMLExtendedStreamReader reader, PatchElementImpl patchElement, Patch.PatchType patchType) throws XMLStreamException {
String name = null;
boolean isAddOn = false;
final int count = reader.getAttributeCount();
for (int i = 0; i < count; i++) {
final Attribute attribute = Attribute.forName(reader.getAttributeLocalName(i));
if (Attribute.NAME == attribute) {
name = reader.getAttributeValue(i);
} else if (Attribute.ADD_ON == attribute) {
isAddOn = Boolean.valueOf(reader.getAttributeValue(i));
} else {
throw unexpectedAttribute(reader, i);
}
}
final PatchElementProviderImpl provider = new PatchElementProviderImpl(name, isAddOn);
patchElement.setProvider(provider);
switch (patchType) {
case CUMULATIVE:
provider.upgrade();
break;
case ONE_OFF:
provider.oneOffPatch();
break;
default:
throw new IllegalStateException();
}
int level = 0;
while (reader.hasNext()) {
if(reader.nextTag() == END_ELEMENT) {
if(level == 0) {
break;
} else {
--level;
}
}
final Element element = Element.forName(reader.getLocalName());
switch (element) {
case REQUIRES:
break;
case PATCH:
level = 1;
parseIncluded(reader, provider);
break;
default:
throw unexpectedElement(reader);
}
}
}
static void parseIdentity(final XMLExtendedStreamReader reader, final PatchBuilder builder, final Patch.PatchType patchType) throws XMLStreamException {
String name = null;
String version = null;
String resultingVersion = null;
final int count = reader.getAttributeCount();
for (int i = 0; i < count; i++) {
final Attribute attribute = Attribute.forName(reader.getAttributeLocalName(i));
if (Attribute.VERSION == attribute) {
version = reader.getAttributeValue(i);
} else if (Attribute.TO_VERSION == attribute) {
resultingVersion = reader.getAttributeValue(i);
} else if (Attribute.NAME == attribute) {
name = reader.getAttributeValue(i);
} else {
throw unexpectedAttribute(reader, i);
}
}
final PatchIdentityBuilder identityBuilder;
switch (patchType) {
case CUMULATIVE:
identityBuilder = builder.upgradeIdentity(name, version, resultingVersion);
break;
case ONE_OFF:
identityBuilder = builder.oneOffPatchIdentity(name, version);
break;
default:
throw new IllegalStateException();
}
int level = 0;
while (reader.hasNext()) {
if(reader.nextTag() == END_ELEMENT) {
if(level == 0) {
break;
} else {
--level;
}
}
final Element element = Element.forName(reader.getLocalName());
switch (element) {
case REQUIRES:
break;
case PATCH:
level = 1;
parseIncluded(reader, identityBuilder);
break;
default:
throw unexpectedElement(reader);
}
}
}
static void parseIncluded(final XMLExtendedStreamReader reader, final RequiresCallback includes) throws XMLStreamException {
final int count = reader.getAttributeCount();
for (int i = 0; i < count; i++) {
final String value = reader.getAttributeValue(i);
final Attribute attribute = Attribute.forName(reader.getAttributeLocalName(i));
if(Attribute.ID == attribute) {
includes.require(value);
} else {
throw unexpectedAttribute(reader, i);
}
}
requireNoContent(reader);
}
static void parseModules(final XMLExtendedStreamReader reader, final Collection<ContentModification> modifications) throws XMLStreamException {
while (reader.hasNext() && reader.nextTag() != END_ELEMENT) {
final Element element = Element.forName(reader.getLocalName());
switch (element) {
case ADDED:
modifications.add(parseModuleModification(reader, ModificationType.ADD));
break;
case UPDATED:
modifications.add(parseModuleModification(reader, ModificationType.MODIFY));
break;
case REMOVED:
modifications.add(parseModuleModification(reader, ModificationType.REMOVE));
break;
default:
throw unexpectedElement(reader);
}
}
}
static void parseMiscFiles(final XMLExtendedStreamReader reader, final Collection<ContentModification> modifications) throws XMLStreamException {
while (reader.hasNext() && reader.nextTag() != END_ELEMENT) {
final Element element = Element.forName(reader.getLocalName());
switch (element) {
case ADDED:
modifications.add(parseMiscModification(reader, ModificationType.ADD));
break;
case UPDATED:
modifications.add(parseMiscModification(reader, ModificationType.MODIFY));
break;
case REMOVED:
modifications.add(parseMiscModification(reader, ModificationType.REMOVE));
break;
default:
throw unexpectedElement(reader);
}
}
}
static void parseBundles(final XMLExtendedStreamReader reader, final Collection<ContentModification> modifications) throws XMLStreamException {
while (reader.hasNext() && reader.nextTag() != END_ELEMENT) {
final Element element = Element.forName(reader.getLocalName());
switch (element) {
case ADDED:
modifications.add(parseBundleModification(reader, ModificationType.ADD));
break;
case UPDATED:
modifications.add(parseBundleModification(reader, ModificationType.MODIFY));
break;
case REMOVED:
modifications.add(parseBundleModification(reader, ModificationType.REMOVE));
break;
default:
throw unexpectedElement(reader);
}
}
}
static ContentModification parseBundleModification(final XMLExtendedStreamReader reader, final ModificationType type) throws XMLStreamException {
return parseSlottedItem(reader, type, ContentType.BUNDLE);
}
static ContentModification parseModuleModification(final XMLExtendedStreamReader reader, final ModificationType type) throws XMLStreamException {
return parseSlottedItem(reader, type, ContentType.MODULE);
}
static ContentModification parseSlottedItem(final XMLExtendedStreamReader reader, ModificationType modificationType, ContentType contentType) throws XMLStreamException {
String moduleName = null;
String slot = "main";
byte[] hash = NO_CONTENT;
byte[] targetHash = NO_CONTENT;
final int count = reader.getAttributeCount();
for (int i = 0; i < count; i++) {
final Attribute attribute = Attribute.forName(reader.getAttributeLocalName(i));
switch (attribute) {
case NAME:
moduleName = reader.getAttributeValue(i);
break;
case SLOT:
slot = reader.getAttributeValue(i);
break;
case HASH:
if (modificationType == ModificationType.REMOVE) {
targetHash = hexStringToByteArray(reader.getAttributeValue(i));
} else {
hash = hexStringToByteArray(reader.getAttributeValue(i));
}
break;
case NEW_HASH:
if (modificationType == ModificationType.REMOVE) {
hash = hexStringToByteArray(reader.getAttributeValue(i));
} else {
targetHash = hexStringToByteArray(reader.getAttributeValue(i));
}
break;
default:
throw unexpectedAttribute(reader, i);
}
}
requireNoContent(reader);
final ModuleItem item = contentType == ContentType.MODULE ? new ModuleItem(moduleName, slot, hash) : new BundleItem(moduleName, slot, hash);
return new ContentModification(item, targetHash, modificationType);
}
static ContentModification parseMiscModification(final XMLExtendedStreamReader reader, ModificationType type) throws XMLStreamException {
String path = null;
byte[] hash = NO_CONTENT;
boolean directory = false;
boolean affectsRuntime = false;
byte[] targetHash = NO_CONTENT;
ModificationCondition condition = null;
final int count = reader.getAttributeCount();
for (int i = 0; i < count; i++) {
final Attribute attribute = Attribute.forName(reader.getAttributeLocalName(i));
switch (attribute) {
case DIRECTORY:
directory = Boolean.parseBoolean(reader.getAttributeValue(i));
break;
case PATH:
path = reader.getAttributeValue(i);
break;
case HASH:
if (type == ModificationType.REMOVE) {
targetHash = hexStringToByteArray(reader.getAttributeValue(i));
} else {
hash = hexStringToByteArray(reader.getAttributeValue(i));
}
break;
case NEW_HASH:
if (type == ModificationType.REMOVE) {
hash = hexStringToByteArray(reader.getAttributeValue(i));
} else {
targetHash = hexStringToByteArray(reader.getAttributeValue(i));
}
break;
case IN_RUNTIME_USE:
affectsRuntime = Boolean.parseBoolean(reader.getAttributeValue(i));
break;
case CONDITION:
try {
condition = ModificationCondition.Factory.fromString(reader.getAttributeValue(i));
} catch (PatchingException e) {
throw ControllerLogger.ROOT_LOGGER.invalidAttributeValue(reader.getAttributeValue(i), new QName(
attribute.name), reader.getLocation());
}
break;
default:
throw unexpectedAttribute(reader, i);
}
}
requireNoContent(reader);
final MiscContentItem item = createMiscItem(path, hash, directory, affectsRuntime);
return new ContentModification(item, targetHash, type, condition);
}
private static MiscContentItem createMiscItem(String path, byte[] hash, boolean directory, boolean affectsRuntime) {
final String[] s = path.split(PATH_DELIMITER);
final int length = s.length;
final String name = s[length - 1];
final String[] itemPath = Arrays.copyOf(s, length - 1);
return new MiscContentItem(name, itemPath, hash, directory, affectsRuntime);
}
protected static void writeAppliesToVersions(XMLExtendedStreamWriter writer, List<String> appliesTo) throws XMLStreamException {
for (String version : appliesTo) {
// writer.writeStartElement(Element.APPLIES_TO_VERSION.name);
writer.writeCharacters(version);
writer.writeEndElement();
}
}
protected static void writeSlottedItems(final XMLExtendedStreamWriter writer, final Element element, final List<ContentModification> modifications) throws XMLStreamException {
for(final ContentModification modification : modifications) {
writeSlottedItem(writer, element, modification);
}
}
protected static void writeSlottedItem(final XMLExtendedStreamWriter writer, Element element, ContentModification modification) throws XMLStreamException {
writer.writeEmptyElement(element.name);
final ModuleItem item = (ModuleItem) modification.getItem();
final ModificationType type = modification.getType();
writer.writeAttribute(Attribute.NAME.name, item.getName());
if (!MAIN_SLOT.equals(item.getSlot())) {
writer.writeAttribute(Attribute.SLOT.name, item.getSlot());
}
byte[] hash = item.getContentHash();
if (hash.length > 0 && type != ModificationType.REMOVE) {
writer.writeAttribute(Attribute.HASH.name, bytesToHexString(hash));
}
if(type == ModificationType.REMOVE) {
final byte[] existingHash = modification.getTargetHash();
if (existingHash.length > 0) {
writer.writeAttribute(Attribute.HASH.name, bytesToHexString(existingHash));
}
} else if(type == ModificationType.MODIFY) {
final byte[] existingHash = modification.getTargetHash();
if (existingHash.length > 0) {
writer.writeAttribute(Attribute.NEW_HASH.name, bytesToHexString(existingHash));
}
}
}
protected static void writeMiscItems(final XMLExtendedStreamWriter writer, final Element element, final List<ContentModification> modifications) throws XMLStreamException {
for(final ContentModification modification : modifications) {
writeMiscItem(writer, element, modification);
}
}
protected static void writeMiscItem(final XMLExtendedStreamWriter writer, final Element element, final ContentModification modification) throws XMLStreamException {
writer.writeEmptyElement(element.name);
final MiscContentItem item = (MiscContentItem) modification.getItem();
final ModificationType type = modification.getType();
final StringBuilder path = new StringBuilder();
for(final String p : item.getPath()) {
path.append(p).append(PATH_DELIMITER);
}
path.append(item.getName());
writer.writeAttribute(Attribute.PATH.name, path.toString());
if (item.isDirectory()) {
writer.writeAttribute(Attribute.DIRECTORY.name, "true");
}
final ModificationCondition condition = modification.getCondition();
if(condition != null) {
writer.writeAttribute(Attribute.CONDITION.name, condition.toString());
}
if(type == ModificationType.REMOVE) {
final byte[] existingHash = modification.getTargetHash();
if (existingHash.length > 0) {
writer.writeAttribute(Attribute.HASH.name, bytesToHexString(existingHash));
}
if(item.isAffectsRuntime()) {
writer.writeAttribute(Attribute.IN_RUNTIME_USE.name, "true");
}
} else {
byte[] hash = item.getContentHash();
if(hash.length > 0) {
writer.writeAttribute(Attribute.HASH.name, bytesToHexString(hash));
}
if(type == ModificationType.MODIFY) {
writer.writeAttribute(Attribute.NEW_HASH.name, bytesToHexString(modification.getTargetHash()));
if (item.isAffectsRuntime()) {
writer.writeAttribute(Attribute.IN_RUNTIME_USE.name, "true");
}
}
}
}
}