blob: 3c482e9ab0360f42cd37663acff8414bcc58b7d2 [file] [log] [blame]
/*
* 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.sling.installer.core.impl;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import org.jetbrains.annotations.Nullable;
import org.apache.sling.installer.api.InstallableResource;
import org.apache.sling.installer.api.tasks.ResourceState;
import org.apache.sling.installer.api.tasks.TaskResource;
import org.apache.sling.installer.api.tasks.TransformationResult;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.Version;
/**
* Implementation of the registered resource
*/
public class RegisteredResourceImpl
implements TaskResource, Serializable, Comparable<RegisteredResourceImpl> {
/** Use own serial version ID as we control serialization. */
private static final long serialVersionUID = 6L;
/** Serialization version. */
private static final int VERSION = 4;
/** The resource url. */
private String url;
/** The installer scheme. */
private String urlScheme;
/** The digest for the resource. */
private String digest;
/** The entity id. */
private String entity;
/** The dictionary for configurations. */
private final Dictionary<String, Object> dictionary;
/** Additional attributes. */
private final Map<String, Object> attributes = new HashMap<>();
private String dataUri;
private File dataFile;
private int priority;
private String resourceType;
/** The current state of this resource. */
private ResourceState state = ResourceState.INSTALL;
/** Temporary attributes. */
private transient Map<String, Object> temporaryAttributes;
private boolean cleanedUp = false;
/** When was the last status change? */
private long lastChange = -1;
/** the potential error related to this resource */
private String error;
/**
* Serialize the object
* - write version id
* - serialize each entry in the resources list
* @param out Object output stream
* @throws IOException
*/
private void writeObject(final java.io.ObjectOutputStream out)
throws IOException {
out.writeInt(VERSION);
out.writeObject(url);
out.writeObject(urlScheme);
out.writeObject(digest);
out.writeObject(entity);
out.writeObject(dictionary);
out.writeObject(attributes);
out.writeObject(dataFile);
out.writeObject(resourceType);
out.writeInt(priority);
out.writeObject(state.toString());
out.writeLong(this.lastChange);
out.writeObject(this.dataUri);
out.writeObject(error);
}
/**
* Deserialize the object
* - read version id
* - deserialize each entry in the resources list
*/
private void readObject(final java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException {
final int version = in.readInt();
if ( version < 1 || version > VERSION ) {
throw new ClassNotFoundException(this.getClass().getName());
}
Util.setField(this, "url", in.readObject());
Util.setField(this, "urlScheme", in.readObject());
Util.setField(this, "digest", in.readObject());
Util.setField(this, "entity", in.readObject());
Util.setField(this, "dictionary", in.readObject());
Util.setField(this, "attributes", in.readObject());
Util.setField(this, "dataFile", in.readObject());
Util.setField(this, "resourceType", in.readObject());
Util.setField(this, "priority", in.readInt());
this.state = ResourceState.valueOf((String) in.readObject());
if ( version > 1 ) {
this.lastChange = in.readLong();
} else {
this.lastChange = 0;
}
if ( version > 2 ) {
this.dataUri = (String)in.readObject();
} else if ( InstallableResource.TYPE_CONFIG.equals(this.resourceType) && this.dictionary != null ) {
// update digest calculated by older versions
final String updatedDigest = FileDataStore.computeDigest(this.dictionary);
if ( !updatedDigest.equals(this.digest) ) {
this.digest = updatedDigest;
}
}
if (version > 3) {
error = (String)in.readObject();
} else {
error = "";
}
// update file location
if ( this.dataFile != null ) {
this.dataFile = FileDataStore.SHARED.getDataFile(this.dataFile.getName());
}
}
/**
* Try to create a registered resource.
*/
public static RegisteredResourceImpl create(
final InternalResource input)
throws IOException {
final int schemePos = input.getURL().indexOf(':');
return new RegisteredResourceImpl(input.getId(),
input.getResourceUri(),
input.getPrivateCopyOfFile(),
input.getPrivateCopyOfDictionary(),
input.getType(),
input.getDigest(),
input.getPriority(),
input.getURL().substring(0, schemePos));
}
/**
* Create a RegisteredResource from given data.
* As this data object is filled from an {@link #create(BundleContext, InstallableResource, String)}
* we don't have to validate values - this has already been done
* The only exception is the digest!
*/
private RegisteredResourceImpl(final String id,
final String resourceUri,
final File file,
final Dictionary<String, Object> dict,
final String type,
final String digest,
final int priority,
final String scheme) {
this.url = scheme + ':' + id;
this.dataUri = resourceUri;
this.dataFile = file;
this.dictionary = dict;
this.resourceType = type;
this.digest = digest;
this.priority = priority;
this.urlScheme = scheme;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
if ( this.getEntityId() == null ) {
sb.append("RegisteredResource");
} else {
sb.append("TaskResource");
}
sb.append("(url=");
sb.append(this.getURL());
if ( this.getEntityId() != null ) {
sb.append(", entity=");
sb.append(this.getEntityId());
sb.append(", state=");
sb.append(this.state);
if ( this.attributes.size() > 0 ) {
sb.append(", attributes=[");
boolean first = true;
for(final Map.Entry<String, Object> entry : this.attributes.entrySet()) {
if ( !first ) {
sb.append(", ");
}
first = false;
sb.append(entry.getKey());
sb.append("=");
sb.append(entry.getValue());
}
sb.append("]");
}
}
sb.append(", digest=");
sb.append(this.getDigest());
sb.append(')');
return sb.toString();
}
public boolean hasDataFile() {
return this.dataFile != null;
}
public File getDataFile() {
return this.dataFile;
}
public String getDataURI() {
return this.dataUri;
}
/**
* Remove the data file
*/
private void removeDataFile() {
if ( this.dataFile != null && this.dataFile.exists() ) {
this.dataFile.delete();
}
this.dataUri = null;
}
/**
* Clean up used data files.
*/
public void cleanup() {
if ( !cleanedUp ) {
cleanedUp = true;
this.removeDataFile();
FileDataStore.SHARED.removeFromDigestCache(this.url, this.digest);
}
}
/**
* @see org.apache.sling.installer.api.tasks.RegisteredResource#getURL()
*/
@Override
public String getURL() {
return this.url;
}
/**
* @see org.apache.sling.installer.api.tasks.RegisteredResource#getInputStream()
*/
@Override
public InputStream getInputStream() throws IOException {
if ( this.dataUri != null ) {
try {
final URI uri = new URI(this.dataUri);
return uri.toURL().openStream();
} catch (final URISyntaxException use) {
throw (IOException)new IOException().initCause(use);
}
}
if (this.dataFile != null && this.dataFile.exists() ) {
return new BufferedInputStream(new FileInputStream(this.dataFile));
}
return null;
}
/**
* @see org.apache.sling.installer.api.tasks.RegisteredResource#getDictionary()
*/
@Override
public Dictionary<String, Object> getDictionary() {
return dictionary;
}
/**
* @see org.apache.sling.installer.api.tasks.RegisteredResource#getDigest()
*/
@Override
public String getDigest() {
return digest;
}
/**
* @see org.apache.sling.installer.api.tasks.RegisteredResource#getType()
*/
@Override
public String getType() {
return resourceType;
}
/**
* @see org.apache.sling.installer.api.tasks.RegisteredResource#getEntityId()
*/
@Override
public String getEntityId() {
return entity;
}
/**
* @see org.apache.sling.installer.api.tasks.TaskResource#getAttribute(java.lang.String)
*/
@Override
public Object getAttribute(final String key) {
return this.attributes.get(key);
}
/**
* @see org.apache.sling.installer.api.tasks.TaskResource#setAttribute(java.lang.String, java.lang.Object)
*/
@Override
public void setAttribute(final String key, final Object value) {
if ( value == null ) {
this.attributes.remove(key);
} else {
this.attributes.put(key, value);
}
}
/**
* @see org.apache.sling.installer.api.tasks.RegisteredResource#getScheme()
*/
@Override
public String getScheme() {
return urlScheme;
}
/**
* @see org.apache.sling.installer.api.tasks.RegisteredResource#getPriority()
*/
@Override
public int getPriority() {
return priority;
}
/**
* @see org.apache.sling.installer.api.tasks.TaskResource#getState()
*/
@Override
public ResourceState getState() {
return this.state;
}
/**
* Set the state for the resource.
*/
public void setState(final ResourceState s, String error) {
this.lastChange = System.currentTimeMillis();
this.state = s;
this.error = error;
}
/**
* When did the last change happen?
* @return -1 if no change , 0 if unknown, > 0 otherwise
*/
public long getLastChange() {
return this.lastChange;
}
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if ( obj == this ) {
return true;
}
if ( ! (obj instanceof RegisteredResourceImpl) ) {
return false;
}
if ( this.entity == null ) {
return this.getURL().equals(((RegisteredResourceImpl)obj).getURL());
}
return compareTo((RegisteredResourceImpl)obj) == 0;
}
/**
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return this.getURL().hashCode();
}
/**
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
@Override
public int compareTo(final RegisteredResourceImpl b) {
return compare(this, b);
}
/**
* Compare two resources where we know that they point to the same entity and have the
* same URL!
*/
public static boolean isSameResource(final RegisteredResourceImpl a, final RegisteredResourceImpl b) {
// check if the artifacts have a version
final Version va = a.getVersion();
final Version vb = b.getVersion();
if ( va != null && vb != null ) {
// Compare version
if ( !vb.equals(va) ) {
return false;
}
final boolean isSnapshot = va.toString().contains("SNAPSHOT");
if ( !isSnapshot ) {
return true;
}
}
// we just compare the digest
return a.getDigest().equals(b.getDigest());
}
/**
* Compare resources.
* First we compare the entity id - the entity id contains the resource type
* together with an entity identifier for the to be installed resource like
* the symbolic name of a bundle, the pid for a configuration etc.
*/
public static int compare(final TaskResource a, final TaskResource b) {
int result = 0;
// check entity id first
final String aId = a.getEntityId();
final String bId = b.getEntityId();
if(aId != null && bId != null) {
result = aId.compareTo(bId);
}
boolean hasVersion = false;
if ( result == 0 ) {
// compare versions
boolean isSnapshot = false;
// Order by version
final Version va = a.getVersion();
final Version vb = b.getVersion();
if ( va != null && vb != null ) {
hasVersion = true;
isSnapshot = va.toString().contains("SNAPSHOT");
// higher version has more priority, must come first so invert comparison
result = vb.compareTo(va);
}
// Then by priority, higher values first
if (result == 0) {
result = Integer.valueOf(b.getPriority()).compareTo(a.getPriority());
}
if (result == 0 && isSnapshot) {
// higher digest has more priority, must come first so invert comparison
result = b.getDigest().compareTo(a.getDigest());
}
}
if ( result == 0 && a.getState() != b.getState() ) {
if ( a.getState() == ResourceState.INSTALLED ) {
return -1;
} else if ( b.getState() == ResourceState.INSTALLED ) {
return 1;
} else if ( a.getState() == ResourceState.INSTALL ) {
return -1;
} else if ( b.getState() == ResourceState.INSTALL ) {
return 1;
}
}
if ( result == 0 ) {
// finally use url and then digest
result = a.getURL().compareTo(b.getURL());
if ( result == 0 && !hasVersion ) {
// higher digest has more priority, must come first so invert comparison
result = b.getDigest().compareTo(a.getDigest());
}
}
return result;
}
/**
* @see org.apache.sling.installer.api.tasks.TaskResource#getTemporaryAttribute(java.lang.String)
*/
@Override
public Object getTemporaryAttribute(final String key) {
if ( this.temporaryAttributes != null ) {
return this.temporaryAttributes.get(key);
}
return null;
}
/**
* @see org.apache.sling.installer.api.tasks.TaskResource#setTemporaryAttribute(java.lang.String, java.lang.Object)
*/
@Override
public void setTemporaryAttribute(final String key, final Object value) {
if ( this.temporaryAttributes == null ) {
this.temporaryAttributes = new HashMap<>();
}
if ( value == null ) {
this.temporaryAttributes.remove(key);
} else {
this.temporaryAttributes.put(key, value);
}
}
/**
* Update this resource from the result.
* Currently only the input stream and resource type is updated.
* @param tr Transformation result
*/
private void update(final TransformationResult tr)
throws IOException {
final InputStream is = tr.getInputStream();
if ( tr.getResourceType() != null ) {
this.resourceType = tr.getResourceType();
if ( tr.getId() != null ) {
this.entity = this.resourceType + ':' + tr.getId();
} else {
if ( !InstallableResource.TYPE_FILE.equals(this.getType())
&& !InstallableResource.TYPE_PROPERTIES.equals(this.getType()) ) {
String lastIdPart = this.getURL();
final int slashPos = lastIdPart.lastIndexOf('/');
if ( slashPos != -1 ) {
lastIdPart = lastIdPart.substring(slashPos + 1);
}
this.entity = this.resourceType + ':' + lastIdPart;
}
}
}
if ( is != null ) {
try {
final File newDataFile = FileDataStore.SHARED.createNewDataFile(this.getType(), is);
this.removeDataFile();
this.dataFile = newDataFile;
} finally {
try {
is.close();
} catch (final IOException ignore) {}
}
}
if ( tr.getAttributes() != null ) {
this.attributes.putAll(tr.getAttributes());
}
if ( tr.getVersion() != null ) {
this.attributes.put(Constants.BUNDLE_VERSION, tr.getVersion().toString());
}
}
/**
* Update the resource uri - if provided.
*/
public void update(final InternalResource rsrc) {
if ( rsrc.getResourceUri() != null ) {
FileDataStore.SHARED.removeFromDigestCache(this.url, this.digest);
this.removeDataFile();
this.dataUri = rsrc.getResourceUri();
if ( this.dictionary != null ) {
this.dictionary.put(InstallableResource.RESOURCE_URI_HINT, rsrc.getResourceUri());
}
} else if ( rsrc.getPrivateCopyOfFile() != null ) {
final boolean update = this.dataFile == null || !this.dataFile.getName().equals(rsrc.getPrivateCopyOfFile().getName());
if ( update ) {
if ( this.dictionary != null ) {
this.dictionary.remove(InstallableResource.RESOURCE_URI_HINT);
}
this.removeDataFile();
this.dataFile = rsrc.getPrivateCopyOfFile();
FileDataStore.SHARED.updateDigestCache(this.url, this.dataFile, this.digest);
}
}
}
/**
* Update the resource uri - if provided.
*/
public void updateResourceUri(final String updatedResourceUri) {
if ( updatedResourceUri != null ) {
FileDataStore.SHARED.removeFromDigestCache(this.url, this.digest);
this.removeDataFile();
this.dataUri = updatedResourceUri;
if ( this.dictionary != null ) {
this.dictionary.put(InstallableResource.RESOURCE_URI_HINT, updatedResourceUri);
}
}
}
/**
* Create a new resource with updated information
*/
public TaskResource clone(TransformationResult transformationResult)
throws IOException {
final int schemePos = this.url.indexOf(':');
final RegisteredResourceImpl rr = new RegisteredResourceImpl(
this.url.substring(schemePos + 1),
this.dataUri,
this.dataFile,
this.dictionary,
this.resourceType,
this.digest,
this.priority,
this.urlScheme);
rr.attributes.putAll(this.attributes);
rr.update(transformationResult);
return rr;
}
public void update(final File file,
final Dictionary<String, Object> dict,
final String digest,
final int priority,
final String url) {
this.removeDataFile();
if ( file != null ) {
this.dataFile = file;
} else {
while ( !this.dictionary.isEmpty() ) {
this.dictionary.remove(this.dictionary.keys().nextElement());
}
final Enumeration<String> keys = dict.keys();
while ( keys.hasMoreElements() ) {
final String key = keys.nextElement();
this.dictionary.put(key, dict.get(key));
}
}
this.digest = digest;
this.priority = priority;
this.url = url;
final int pos = url.indexOf(':');
this.urlScheme = url.substring(0, pos);
}
/**
* @see org.apache.sling.installer.api.tasks.TaskResource#getVersion()
*/
@Override
public Version getVersion() {
final String vInfo = (String)this.getAttribute(Constants.BUNDLE_VERSION);
return (vInfo == null ? null : new Version(vInfo));
}
@Override
@Nullable
public String getError() {
return error;
}
}