/*
* 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.resourcemerger.impl;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.sling.api.resource.ModifiableValueMap;
import org.apache.sling.api.resource.PersistenceException;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceUtil;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.api.wrappers.DeepReadValueMapDecorator;
import org.apache.sling.api.wrappers.ValueMapDecorator;
import org.apache.sling.resourcemerger.spi.MergedResourcePicker2;
/**
* {@inheritDoc}
*/
public class CRUDMergedResource extends MergedResource {
private final MergedResourcePicker2 picker;
private final String relativePath;
/**
* Constructor
*
* @param resolver Resource resolver
* @param mergeRootPath Merge root path
* @param relativePath Relative path
* @param mappedResources List of physical mapped resources' paths
*/
CRUDMergedResource(final ResourceResolver resolver,
final String mergeRootPath,
final String relativePath,
final List<Resource> mappedResources,
final List<ValueMap> valueMaps,
final MergedResourcePicker2 picker) {
super(resolver, mergeRootPath, relativePath, mappedResources, valueMaps);
this.picker = picker;
this.relativePath = relativePath;
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("unchecked")
public <AdapterType> AdapterType adaptTo(final Class<AdapterType> type) {
if (type == ModifiableValueMap.class) {
final Iterator<Resource> iter = this.picker.pickResources(this.getResourceResolver(), this.relativePath, null).iterator();
Resource highestRsrc = null;
while ( iter.hasNext() ) {
highestRsrc = iter.next();
}
if ( ResourceUtil.isNonExistingResource(highestRsrc) ) {
final String paths[] = (String[])this.getResourceMetadata().get(MergedResourceConstants.METADATA_RESOURCES);
final Resource copyResource = this.getResourceResolver().getResource(paths[paths.length - 1]);
try {
final Resource newResource = ResourceUtil.getOrCreateResource(this.getResourceResolver(), highestRsrc.getPath(), copyResource.getResourceType(), null, false);
final ModifiableValueMap target = newResource.adaptTo(ModifiableValueMap.class);
if ( target != null ) {
return (AdapterType)new ModifiableProperties(this, target);
}
} catch ( final PersistenceException pe) {
// we ignore this for now
}
return super.adaptTo(type);
}
final ModifiableValueMap target = highestRsrc.adaptTo(ModifiableValueMap.class);
if ( target != null ) {
return (AdapterType)new ModifiableProperties(this, target);
}
}
return super.adaptTo(type);
}
private static final class ModifiableProperties implements ModifiableValueMap {
private final ModifiableValueMap targetMap;
private final ValueMap properties;
public ModifiableProperties(final Resource rsrc, final ModifiableValueMap targetMap) {
this.properties = new DeepReadValueMapDecorator(rsrc, new ValueMapDecorator(new HashMap<String, Object>(rsrc.getValueMap())));
this.targetMap = targetMap;
}
@Override
public <T> T get(final String name, final Class<T> type) {
return properties.get(name, type);
}
@Override
public <T> T get(final String name, final T defaultValue) {
return properties.get(name, defaultValue);
}
@Override
public int size() {
return properties.size();
}
@Override
public boolean isEmpty() {
return properties.isEmpty();
}
@Override
public boolean containsKey(final Object key) {
return properties.containsKey(key);
}
@Override
public boolean containsValue(final Object value) {
return properties.containsValue(value);
}
@Override
public Object get(final Object key) {
return properties.get(key);
}
@Override
public Object put(final String key, final Object value) {
final Object result = this.properties.get(key);
this.targetMap.put(key, value);
return result;
}
@Override
public Object remove(final Object key) {
final Object result = this.properties.get(key);
if ( this.targetMap.remove(key) == null ) {
final String[] hiddenProps = this.targetMap.get(MergedResourceConstants.PN_HIDE_PROPERTIES, String[].class);
final String[] newHiddenProps;
if ( hiddenProps == null || hiddenProps.length == 0 ) {
newHiddenProps = new String[] {key.toString()};
} else {
newHiddenProps = new String[hiddenProps.length + 1];
System.arraycopy(hiddenProps, 0, newHiddenProps, 0, hiddenProps.length);
newHiddenProps[hiddenProps.length] = key.toString();
}
this.targetMap.put(MergedResourceConstants.PN_HIDE_PROPERTIES, newHiddenProps);
}
return result;
}
@Override
public void putAll(final Map<? extends String, ? extends Object> m) {
if ( m != null ) {
for(final Map.Entry<? extends String, ? extends Object> entry : m.entrySet()) {
this.put(entry.getKey(), entry.getValue());
}
}
}
@Override
public void clear() {
// not supported
}
@Override
public Set<String> keySet() {
return this.properties.keySet();
}
@Override
public Collection<Object> values() {
return this.properties.values();
}
@Override
public Set<Entry<String, Object>> entrySet() {
return this.properties.entrySet();
}
}
}