/*
 * #%L
 * ACS AEM Commons Bundle
 * %%
 * Copyright (C) 2014 Adobe
 * %%
 * Licensed 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.
 * #L%
 */
package com.adobe.acs.commons.oak.impl;

import java.util.Map;

import javax.jcr.Node;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
import javax.jcr.ValueFactory;

import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.PropertyUnbounded;
import org.apache.felix.scr.annotations.Reference;
import org.apache.sling.commons.osgi.PropertiesUtil;
import org.apache.sling.jcr.api.SlingRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.adobe.acs.commons.util.AemCapabilityHelper;

@Component(configurationFactory = true, metatype = true, label = "ACS AEM Commons - Ensure Oak Property Index",
        description = "Component Factory to create Oak property indexes.")
public class EnsurePropertyIndex {

    private static class IndexDefinition {
        private boolean async;
        private String[] declaringNodeTypes;
        private String propertyName;
        private boolean unique;
    }

    private static final boolean DEFAULT_ASYNC = false;

    private static final boolean DEFAULT_UNIQUE = false;

    private static final Logger log = LoggerFactory.getLogger(EnsurePropertyIndex.class);

    private static final String NN_OAK_INDEX = "oak:index";

    private static final String NT_QID = "oak:QueryIndexDefinition";

    private static final String PN_ASYNC = "async";

    private static final String PN_DECLARING_NODE_TYPES = "declaringNodeTypes";

    private static final String PN_PROPERTY_NAMES = "propertyNames";

    private static final String PN_REINDEX = "reindex";

    private static final String PN_TYPE = "type";

    private static final String PN_UNIQUE = "unique";

    @Property(label = "Async?", description = "Is this index async?", boolValue = DEFAULT_ASYNC)
    private static final String PROP_ASYNC = "index.async";

    @Property(label = "Index Name", description = "Will be used as the index node name.")
    private static final String PROP_INDEX_NAME = "index.name";

    @Property(label = "Declaring Node Types", description = "Declaring Node Types", unbounded = PropertyUnbounded.ARRAY)
    private static final String PROP_NODE_TYPES = "node.types";
    
    @Property(label = "Property Name", description = "Property name to index.")
    private static final String PROP_PROPERTY_NAME = "property.name";

    @Property(label = "Unique", description = "Is in this index unique?", boolValue = DEFAULT_UNIQUE)
    private static final String PROP_UNIQUE = "unique";

    private static final String TYPE_PROPERTY = "property";

    @Reference
    private AemCapabilityHelper capabilityHelper;

    @Reference
    private SlingRepository repository;

    private void createOrUpdateIndex(Node indexNode, IndexDefinition def) throws RepositoryException {
        ValueFactory valueFactory = indexNode.getSession().getValueFactory();

        indexNode.setProperty(PN_TYPE, TYPE_PROPERTY);
        indexNode.setProperty(PN_PROPERTY_NAMES,
                new Value[] { valueFactory.createValue(def.propertyName, PropertyType.NAME) });
        if (def.async) {
            indexNode.setProperty(PN_ASYNC, PN_ASYNC);
        } else if (indexNode.hasProperty(PN_ASYNC)) {
            indexNode.getProperty(PN_ASYNC).remove();
        }
        if (def.unique) {
            indexNode.setProperty(PN_UNIQUE, true);
        } else if (indexNode.hasProperty(PN_ASYNC)) {
            indexNode.getProperty(PN_UNIQUE).remove();
        }
        if (def.declaringNodeTypes != null && def.declaringNodeTypes.length > 0) {
            Value[] values = new Value[def.declaringNodeTypes.length];
            for (int i = 0; i < def.declaringNodeTypes.length; i++) {
                values[i] = valueFactory.createValue(def.declaringNodeTypes[0], PropertyType.NAME);
            }
            indexNode.setProperty(PN_DECLARING_NODE_TYPES, values);
        } else if (indexNode.hasProperty(PN_DECLARING_NODE_TYPES)) {
            indexNode.getProperty(PN_DECLARING_NODE_TYPES).remove();
        }
    }

    private boolean needsUpdate(Node indexNode, IndexDefinition def) throws RepositoryException {
        Value[] currentPropertyNames = indexNode.getProperty(PN_PROPERTY_NAMES).getValues();
        return !currentPropertyNames[0].getString().equals(def.propertyName);
    }

    private void updateIndex(Node indexNode, IndexDefinition def) throws RepositoryException {
        createOrUpdateIndex(indexNode, def);
        indexNode.setProperty(PN_REINDEX, true);
    }

    @Activate
    protected void activate(Map<String, Object> properties) throws RepositoryException {
        if (capabilityHelper.isOak()) {
            String name = PropertiesUtil.toString(properties.get(PROP_INDEX_NAME), null);

            IndexDefinition def = new IndexDefinition();
            def.propertyName = PropertiesUtil.toString(properties.get(PROP_PROPERTY_NAME), null);
            def.async = PropertiesUtil.toBoolean(properties.get(PROP_ASYNC), DEFAULT_ASYNC);
            def.unique = PropertiesUtil.toBoolean(properties.get(PROP_UNIQUE), DEFAULT_UNIQUE);
            def.declaringNodeTypes = PropertiesUtil.toStringArray(properties.get(PROP_NODE_TYPES), new String[0]);

            if (name == null || def.propertyName == null) {
                log.warn("Incomplete configure; name or property name is null.");
                return;
            }

            Session session = null;
            try {
                session = repository.loginAdministrative(null);

                Node oakIndexContainer = session.getRootNode().getNode(NN_OAK_INDEX);
                if (oakIndexContainer.hasNode(name)) {
                    Node indexNode = oakIndexContainer.getNode(name);
                    if (needsUpdate(indexNode, def)) {
                        log.info("updating index {}", name);
                        updateIndex(indexNode, def);
                    } else {
                        log.debug("index {} does not need updating", name);
                    }
                } else {
                    log.info("creating index {}", name);
                    createOrUpdateIndex(oakIndexContainer.addNode(name, NT_QID), def);
                }
                session.save();

            } catch (RepositoryException e) {
                log.error("Unable to create index", e);
            } finally {
                if (session != null) {
                    session.logout();
                }
            }
        } else {
            log.info("Cowardly refusing to create indexes on non-Oak instance.");
        }
    }

}

@@ -4183,36 +4183,77 @@
 YNC)) %7B%0A        
-    
+%09if(indexNode.hasNode(PN_UNIQUE))%7B%0A        %09%09
 indexNode.getPro
@@ -4270,32 +4270,44 @@
 NIQUE).remove();
+%09%0A        %09%7D
 %0A        %7D%0A     

Check if property 'unique' exists, before trying to remove.