package org.apache.solr.core;

/*
 * 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.
 */

import static org.apache.solr.common.SolrException.ErrorCode.SERVICE_UNAVAILABLE;
import static org.apache.solr.common.cloud.ZkStateReader.BASE_URL_PROP;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.cloud.ClusterState;
import org.apache.solr.common.cloud.DocCollection;
import org.apache.solr.common.cloud.Replica;
import org.apache.solr.common.cloud.Slice;
import org.apache.solr.handler.admin.CollectionsHandler;
import org.apache.solr.util.SimplePostTool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The purpose of this class is to store the Jars loaded in memory and to keep only one copy of the Jar in a single node.
 */
public class JarRepository {
  public static Logger log = LoggerFactory.getLogger(JarRepository.class);
  
  private final CoreContainer coreContainer;
  
  private Map<String,JarContent> jars = new ConcurrentHashMap<>();
  
  public JarRepository(CoreContainer coreContainer) {
    this.coreContainer = coreContainer;
  }
  
  /**
   * Returns the contents of a jar and increments a reference count. Please return the same object to decerease the refcount
   * 
   * @param key
   *          it is a combination of blobname and version like blobName/version
   * @return The reference of a jar
   */
  public JarContentRef getJarIncRef(String key) throws IOException {
    JarContent jar = jars.get(key);
    if (jar == null) {
      if (this.coreContainer.isZooKeeperAware()) {
        ClusterState cs = this.coreContainer.getZkController().getZkStateReader().getClusterState();
        DocCollection coll = cs.getCollectionOrNull(CollectionsHandler.SYSTEM_COLL);
        if (coll == null) throw new SolrException(SERVICE_UNAVAILABLE, ".system collection not available");
        ArrayList<Slice> slices = new ArrayList<>(coll.getActiveSlices());
        if (slices.isEmpty()) throw new SolrException(SERVICE_UNAVAILABLE, ".no active slices for .system collection");
        Collections.shuffle(slices); //do load balancing
        Slice slice = slices.get(0) ;
        Replica replica = slice.getReplicas().iterator().next();
        if (replica == null) throw new SolrException(SERVICE_UNAVAILABLE, ".no active replica available for .system collection");
        String url = replica.getStr(BASE_URL_PROP) + "/.system/blob/" + key + "?wt=filestream";
        
        HttpClient httpClient = coreContainer.getUpdateShardHandler().getHttpClient();
        HttpGet httpGet = new HttpGet(url);
        ByteBuffer b;
        try {
          HttpResponse entity = httpClient.execute(httpGet);
          int statusCode = entity.getStatusLine().getStatusCode();
          if (statusCode != 200) {
            throw new SolrException(SolrException.ErrorCode.NOT_FOUND, "no such blob or version available: " + key);
          }
          b = SimplePostTool.inputStreamToByteArray(entity.getEntity().getContent());
        } finally {
          httpGet.releaseConnection();
        }
        jars.put(key, jar = new JarContent(key, b));
      } else {
        
        throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Jar loading is not supported in non-cloud mode");
        // todo
        
      }
      
    }
    
    JarContentRef ref = new JarContentRef(jar);
    synchronized (jar.references) {
      jar.references.add(ref);
    }
    return ref;
    
  }
  
  /**
   * This is to decrement a ref count
   * 
   * @param ref
   *          The reference that is already there. Doing multiple calls with same ref will not matter
   */
  public void decrementJarRefCount(JarContentRef ref) {
    if (ref == null) return;
    synchronized (ref.jar.references) {
      if (!ref.jar.references.remove(ref)) {
        log.error("Multiple releases for the same reference");
      }
      if (ref.jar.references.isEmpty()) {
        jars.remove(ref.jar.key);
      }
    }
    
  }
  
  public static class JarContent {
    private final String key;
    // TODO move this off-heap
    private final ByteBuffer buffer;
    // ref counting mechanism
    private final Set<JarContentRef> references = new HashSet<>();
    
    public JarContent(String key, ByteBuffer buffer) {
      this.key = key;
      this.buffer = buffer;
    }
    
    public ByteBuffer getFileContent(String entryName) throws IOException {
      ByteArrayInputStream zipContents = new ByteArrayInputStream(buffer.array(), buffer.arrayOffset(), buffer.limit());
      ZipInputStream zis = new ZipInputStream(zipContents);
      try {
        ZipEntry entry;
        while ((entry = zis.getNextEntry()) != null) {
          if (entryName == null || entryName.equals(entry.getName())) {
            SimplePostTool.BAOS out = new SimplePostTool.BAOS();
            byte[] buffer = new byte[2048];
            int size;
            while ((size = zis.read(buffer, 0, buffer.length)) != -1) {
              out.write(buffer, 0, size);
            }
            out.close();
            return out.getByteBuffer();
          }
        }
      } finally {
        zis.closeEntry();
      }
      return null;
    }
    
  }
  
  public static class JarContentRef {
    public final JarContent jar;
    
    private JarContentRef(JarContent jar) {
      this.jar = jar;
    }
  }
  
}

@@ -1177,24 +1177,49 @@
 a.util.Map;%0A
+import java.util.Random;%0A
 import java.
@@ -1224,24 +1224,24 @@
 a.util.Set;%0A
-
 import java.
@@ -2096,16 +2096,369 @@
 .class);
+%0A%0A  static final Random RANDOM;%0A  static %7B%0A    // We try to make things reproducible in the context of our tests by initializing the random instance%0A    // based on the current seed%0A    String seed = System.getProperty(%22tests.seed%22);%0A    if (seed == null) %7B%0A      RANDOM = new Random();%0A    %7D else %7B%0A      RANDOM = new Random(seed.hashCode());%0A    %7D%0A  %7D
 %0A  %0A  pr
@@ -3607,32 +3607,32 @@
 m collection%22);%0A
-
         Collecti
@@ -3649,16 +3649,24 @@
 e(slices
+, RANDOM
 ); //do 

Use static random initialized with random.seed and use it in Collections.shuffle to make forbidden-api checks pass