| /* |
| * 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.BufferedOutputStream; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.ObjectInputStream; |
| import java.io.ObjectOutputStream; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Hashtable; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.apache.sling.installer.api.InstallableResource; |
| import org.apache.sling.installer.api.event.InstallationListener; |
| import org.apache.sling.installer.api.tasks.RegisteredResource; |
| import org.apache.sling.installer.api.tasks.TransformationResult; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * Persistent list of RegisteredResource, used by installer to |
| * keep track of all registered resources |
| */ |
| public class PersistentResourceList { |
| |
| /** Serialization version. */ |
| private static final int VERSION = 2; |
| |
| /** Entity id for restart active bundles. */ |
| public static final String RESTART_ACTIVE_BUNDLES_TYPE = "org.apache.sling.installer.core.restart.bundles"; |
| public static final String RESTART_ACTIVE_BUNDLES_ID = "org.apache.sling.installer.core.restart.bundles"; |
| public static final String RESTART_ACTIVE_BUNDLES_ENTITY_ID = RESTART_ACTIVE_BUNDLES_TYPE + ':' + RESTART_ACTIVE_BUNDLES_ID; |
| |
| /** The logger */ |
| private final Logger logger = LoggerFactory.getLogger(this.getClass()); |
| |
| /** |
| * Map of registered resource sets. |
| * The key of the map is the entity id of the registered resource. |
| * The value is a set containing all registered resources for the |
| * same entity. Usually this is just one resource per entity. |
| */ |
| private final Map<String, EntityResourceList> data; |
| |
| /** The persistence file. */ |
| private final File dataFile; |
| |
| /** All untransformed resources. */ |
| private final List<RegisteredResource> untransformedResources; |
| |
| private final InstallationListener listener; |
| |
| @SuppressWarnings("unchecked") |
| public PersistentResourceList(final File dataFile, final InstallationListener listener) { |
| this.dataFile = dataFile; |
| this.listener = listener; |
| |
| Map<String, EntityResourceList> restoredData = null; |
| List<RegisteredResource> unknownList = null; |
| if ( dataFile.exists() ) { |
| ObjectInputStream ois = null; |
| try { |
| ois = new ObjectInputStream(new BufferedInputStream(new FileInputStream(dataFile))); |
| final int version = ois.readInt(); |
| if ( version > 0 && version <= VERSION ) { |
| restoredData = (Map<String, EntityResourceList>)ois.readObject(); |
| if ( version == VERSION ) { |
| unknownList = (List<RegisteredResource>)ois.readObject(); |
| } |
| } else { |
| logger.warn("Unknown version for persistent resource list: {}", version); |
| } |
| logger.debug("Restored resource list: {}", restoredData); |
| logger.debug("Restored unknown resource list: {}", unknownList); |
| } catch (final Exception e) { |
| logger.warn("Unable to restore data, starting with empty list (" + e.getMessage() + ")", e); |
| restoredData = null; |
| unknownList = null; |
| } finally { |
| if (ois != null) { |
| try { |
| ois.close(); |
| } catch (final IOException ignore) { |
| // ignore |
| } |
| } |
| } |
| } |
| data = restoredData != null ? restoredData : new HashMap<String, EntityResourceList>(); |
| this.untransformedResources = unknownList != null ? unknownList : new ArrayList<RegisteredResource>(); |
| |
| this.updateCache(); |
| |
| // update resource ids |
| for(final Map.Entry<String, EntityResourceList> entry : this.data.entrySet()) { |
| final EntityResourceList erl = entry.getValue(); |
| erl.setResourceId(entry.getKey()); |
| erl.setListener(listener); |
| } |
| |
| // check for special resources |
| if ( this.getEntityResourceList(RESTART_ACTIVE_BUNDLES_ENTITY_ID) == null ) { |
| final RegisteredResource rr = this.addOrUpdate(new InternalResource("$sling-installer$", |
| RESTART_ACTIVE_BUNDLES_ID, |
| null, |
| new Hashtable<String, Object>(), |
| InstallableResource.TYPE_PROPERTIES, "1", |
| null, null, null)); |
| final TransformationResult result = new TransformationResult(); |
| result.setId(RESTART_ACTIVE_BUNDLES_ID); |
| result.setResourceType(RESTART_ACTIVE_BUNDLES_TYPE); |
| this.transform(rr, new TransformationResult[] {result}); |
| } |
| } |
| |
| /** |
| * Update the url to digest cache |
| */ |
| private void updateCache() { |
| for(final EntityResourceList group : this.data.values()) { |
| for(final RegisteredResource rr : group.listResources()) { |
| if ( ((RegisteredResourceImpl)rr).hasDataFile() ) { |
| FileDataStore.SHARED.updateDigestCache(rr.getURL(), ((RegisteredResourceImpl)rr).getDataFile(), rr.getDigest()); |
| } |
| } |
| } |
| for(final RegisteredResource rr : this.untransformedResources ) { |
| if ( ((RegisteredResourceImpl)rr).hasDataFile() ) { |
| FileDataStore.SHARED.updateDigestCache(rr.getURL(), ((RegisteredResourceImpl)rr).getDataFile(), rr.getDigest()); |
| } |
| } |
| } |
| |
| /** |
| * Persist the current state |
| */ |
| public void save() { |
| try { |
| final ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(dataFile))); |
| try { |
| oos.writeInt(VERSION); |
| oos.writeObject(data); |
| oos.writeObject(untransformedResources); |
| logger.debug("Persisted resource list."); |
| } finally { |
| oos.close(); |
| } |
| } catch (final Exception e) { |
| logger.warn("Unable to save persistent list: " + e.getMessage(), e); |
| } |
| } |
| |
| public Collection<String> getEntityIds() { |
| return this.data.keySet(); |
| } |
| |
| /** |
| * Add or update an installable resource. |
| * @param input The installable resource |
| */ |
| public RegisteredResource addOrUpdate(final InternalResource input) { |
| // first check untransformed resource if there are resources with the same url and digest |
| for(final RegisteredResource rr : this.untransformedResources ) { |
| if ( rr.getURL().equals(input.getURL()) && ( rr.getDigest().equals(input.getDigest())) ) { |
| // if we found the resource we can return after updating |
| ((RegisteredResourceImpl)rr).update(input); |
| return rr; |
| } |
| } |
| // installed resources are next |
| for(final EntityResourceList group : this.data.values()) { |
| for(final RegisteredResource rr : group.listResources()) { |
| if ( rr.getURL().equals(input.getURL()) && ( rr.getDigest().equals(input.getDigest()))) { |
| // if we found the resource we can return after updating |
| ((RegisteredResourceImpl)rr).update(input); |
| return rr; |
| } |
| } |
| } |
| try { |
| final RegisteredResourceImpl registeredResource = RegisteredResourceImpl.create(input); |
| this.checkInstallable(registeredResource); |
| return registeredResource; |
| } catch (final IOException ioe) { |
| logger.warn("Ignoring resource. Error during processing of " + input.getURL(), ioe); |
| return null; |
| } |
| } |
| |
| /** |
| * Check if the provided installable resource is already installable (has a |
| * known resource type) |
| */ |
| private void checkInstallable(final RegisteredResourceImpl input) { |
| if ( !InstallableResource.TYPE_FILE.equals(input.getType()) |
| && !InstallableResource.TYPE_PROPERTIES.equals(input.getType()) ) { |
| |
| EntityResourceList t = this.data.get(input.getEntityId()); |
| if (t == null) { |
| t = new EntityResourceList(input.getEntityId(), this.listener); |
| this.data.put(input.getEntityId(), t); |
| } |
| |
| t.addOrUpdate(input); |
| } else { |
| // check if there is an old resource and remove it first |
| if ( this.untransformedResources.contains(input) ) { |
| this.untransformedResources.remove(input); |
| } |
| this.untransformedResources.add(input); |
| } |
| } |
| |
| /** |
| * Get the list of untransformed resources = resources without resource type |
| */ |
| public List<RegisteredResource> getUntransformedResources() { |
| return this.untransformedResources; |
| } |
| |
| /** |
| * Remove a resource by url. |
| * Check all resource groups and the list of untransformed resources. |
| * @param url The url to remove |
| */ |
| public void remove(final String url) { |
| // iterate over all resource groups and remove resources |
| // with the given url |
| for(final EntityResourceList group : this.data.values()) { |
| group.remove(url); |
| } |
| // iterate over untransformed resources and remove |
| // the resource with that url |
| final Iterator<RegisteredResource> i = this.untransformedResources.iterator(); |
| while ( i.hasNext() ) { |
| final RegisteredResource rr = i.next(); |
| if ( rr.getURL().equals(url) ) { |
| ((RegisteredResourceImpl)rr).cleanup(); |
| i.remove(); |
| break; |
| } |
| } |
| } |
| |
| /** |
| * Get the resource group for an entity id. |
| */ |
| public EntityResourceList getEntityResourceList(final String entityId) { |
| EntityResourceList erl = this.data.get(entityId); |
| if ( erl == null ) { |
| for(final EntityResourceList group : this.data.values()) { |
| if ( entityId.equals(group.getFullAlias()) ) { |
| erl = group; |
| break; |
| } |
| } |
| } |
| return erl; |
| } |
| |
| /** |
| * Compact the internal state and remove empty groups. |
| * @return <code>true</code> if another cycle should be started. |
| */ |
| public boolean compact() { |
| boolean startNewCycle = false; |
| final Iterator<Map.Entry<String, EntityResourceList>> i = this.data.entrySet().iterator(); |
| while ( i.hasNext() ) { |
| final Map.Entry<String, EntityResourceList> entry = i.next(); |
| |
| startNewCycle |= entry.getValue().compact(); |
| if ( entry.getValue().isEmpty() ) { |
| i.remove(); |
| } |
| } |
| return startNewCycle; |
| } |
| |
| /** |
| * Transform an unknown resource to a registered one |
| */ |
| public void transform(final RegisteredResource resource, |
| final TransformationResult[] result) { |
| // remove resource from unknown list |
| this.untransformedResources.remove(resource); |
| try { |
| Set<String> entityIds = new HashSet<>(); |
| for(int i=0; i<result.length; i++) { |
| // check the result |
| final TransformationResult tr = result[i]; |
| if ( tr == null ) { |
| logger.warn("Ignoring null result for {}", resource); |
| continue; |
| } |
| if ( tr.getResourceType() != null && tr.getId() == null) { |
| logger.error("Result for {} contains new resource type {} but no unique id!", |
| resource, tr.getResourceType()); |
| continue; |
| } |
| final RegisteredResourceImpl clone = (RegisteredResourceImpl)((RegisteredResourceImpl)resource).clone(result[i]); |
| this.checkInstallable(clone); |
| entityIds.add(clone.getEntityId()); |
| } |
| for (EntityResourceList group : this.data.values()) { |
| if (!entityIds.contains(group.getResourceId())) { |
| if (group.removeInternal(resource.getURL())) { |
| logger.debug("Removed stale resources from group with entityid: {} because after transforming {} the entityids have changed.", group.getResourceId(), resource); |
| } |
| } |
| } |
| } catch (final IOException ioe) { |
| logger.warn("Ignoring resource. Error during processing of " + resource, ioe); |
| } |
| } |
| |
| /** |
| * Check if the id is a special id and should not be included in the info report |
| */ |
| public boolean isSpecialEntityId(final String id) { |
| return RESTART_ACTIVE_BUNDLES_ENTITY_ID.equals(id); |
| } |
| |
| public void update(final String oldId, final String newAlias, final String newId) { |
| final EntityResourceList list = this.data.remove(oldId); |
| if (list != null ) { |
| list.update(newAlias, newId); |
| this.data.put(newId, list); |
| } |
| } |
| } |