| /* |
| * 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.jcr.packageinit.impl; |
| |
| import org.apache.commons.lang3.StringUtils; |
| import org.apache.jackrabbit.vault.packaging.registry.ExecutionPlan; |
| import org.apache.jackrabbit.vault.packaging.registry.ExecutionPlanBuilder; |
| import org.apache.jackrabbit.vault.packaging.registry.PackageRegistry; |
| import org.apache.jackrabbit.vault.packaging.registry.PackageTask; |
| import org.apache.sling.jcr.api.SlingRepository; |
| import org.apache.sling.jcr.api.SlingRepositoryInitializer; |
| import org.osgi.framework.BundleContext; |
| import org.osgi.service.component.annotations.Activate; |
| import org.osgi.service.component.annotations.Component; |
| import org.osgi.service.metatype.annotations.AttributeDefinition; |
| import org.osgi.service.metatype.annotations.Designate; |
| import org.osgi.service.metatype.annotations.ObjectClassDefinition; |
| import org.osgi.util.tracker.ServiceTracker; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import javax.jcr.Session; |
| |
| import java.io.BufferedReader; |
| import java.io.BufferedWriter; |
| import java.io.ByteArrayInputStream; |
| import java.io.File; |
| import java.io.FileNotFoundException; |
| import java.io.FileReader; |
| import java.io.FileWriter; |
| import java.io.IOException; |
| import java.nio.charset.StandardCharsets; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| @Component(service = {SlingRepositoryInitializer.class}, |
| property = {"service.ranking:Integer=200"}) |
| @Designate(ocd = ExecutionPlanRepoInitializer.Config.class) |
| public class ExecutionPlanRepoInitializer implements SlingRepositoryInitializer { |
| |
| private static final String EXECUTEDPLANS_FILE = "executedplans.file"; |
| private List<String> executionPlans = new ArrayList<>(); |
| |
| private File statusFile; |
| |
| @ObjectClassDefinition( |
| name = "Execution plan based Repository Initializer" |
| ) |
| @interface Config { |
| |
| @AttributeDefinition |
| String statusfilepath() default ""; |
| |
| @AttributeDefinition |
| String[] executionplans() default {}; |
| } |
| |
| /** |
| * The logger. |
| */ |
| private final Logger logger = LoggerFactory.getLogger(getClass()); |
| private BundleContext context; |
| |
| @Activate |
| private void activate(BundleContext context, Config config) throws FileNotFoundException, IOException { |
| List<String> epCandidates = Arrays.asList(config.executionplans()); |
| if (!epCandidates.isEmpty()) { |
| if (StringUtils.isEmpty(config.statusfilepath())) { |
| // if no path is configured lookup default file in bundledata |
| statusFile = context.getDataFile(EXECUTEDPLANS_FILE); |
| } else { |
| Path statusFilePath = Paths.get(config.statusfilepath()); |
| if (statusFilePath.isAbsolute()) { |
| // only absolute references are considered for lookup of |
| // external statusfile |
| statusFile = statusFilePath.toFile(); |
| } else { |
| throw new IllegalStateException("Only absolute paths supported"); |
| } |
| } |
| if (statusFile.exists()) { |
| // in case statusFile already exists read all hashes |
| List<Integer> executedHashes = new ArrayList<>(); |
| try (BufferedReader br = new BufferedReader(new FileReader(statusFile))) { |
| for (String line; (line = br.readLine()) != null;) { |
| executedHashes.add(Integer.parseInt(line)); |
| } |
| } |
| processCandidates(epCandidates, executedHashes); |
| } else { |
| this.executionPlans.addAll(epCandidates); |
| } |
| } |
| this.context = context; |
| } |
| |
| private void processCandidates(List<String> epCandidates, List<Integer> executedHashes) { |
| Iterator<String> candidateIt = epCandidates.iterator(); |
| Iterator<Integer> executedHashesIt = executedHashes.iterator(); |
| // iterate over candidates and crosscheck next found hash |
| while (candidateIt.hasNext()) { |
| String candidate = candidateIt.next(); |
| boolean foundDifference = false; |
| if (!executedHashesIt.hasNext() || foundDifference) { |
| // if no further hashes are present add candidate |
| // (will iterate over rest and add rest) |
| executionPlans.add(candidate); |
| } else { |
| // another hash was found & no difference |
| Integer executedHash = executedHashesIt.next(); |
| if (isCandidateProcessed(candidate, executedHash)) { |
| // already processed so no need to add - check |
| // next plan |
| continue; |
| } else { |
| executionPlans.add(candidate); |
| String msg = "Found difference in hashed executionplans - queueing executionplan for processing."; |
| logger.info(msg); |
| foundDifference = true; |
| } |
| } |
| } |
| } |
| |
| private boolean isCandidateProcessed(String candidate, Integer executedHash) { |
| return executedHash.equals(Integer.valueOf(candidate.hashCode())); |
| } |
| |
| @Override |
| public void processRepository(SlingRepository slingRepository) throws Exception { |
| if (!executionPlans.isEmpty()) { |
| ServiceTracker<PackageRegistry, ?> st = new ServiceTracker<>(context, PackageRegistry.class, null); |
| try { |
| st.open(); |
| logger.info("Waiting for PackageRegistry."); |
| PackageRegistry registry = (PackageRegistry) st.waitForService(0); |
| logger.info("PackageRegistry found - starting execution of execution plan"); |
| |
| ExecutionPlanBuilder builder = registry.createExecutionPlan(); |
| @SuppressWarnings("deprecation") |
| Session session = slingRepository.loginAdministrative(null); |
| try (BufferedWriter writer = new BufferedWriter(new FileWriter(statusFile))) { |
| for (String plan : executionPlans) { |
| builder.load(new ByteArrayInputStream(plan.getBytes(StandardCharsets.UTF_8))); |
| builder.with(session); |
| ExecutionPlan xplan = builder.execute(); |
| if (xplan.getTasks().size() > 0) { |
| if (xplan.hasErrors()) { |
| IllegalStateException ex = new IllegalStateException("Execution plan contained errors - cannot complete repository initialization."); |
| for (PackageTask task : xplan.getTasks()) { |
| if (PackageTask.State.ERROR.equals(task.getState())){ |
| ex.addSuppressed(task.getError()); |
| } |
| } |
| throw ex; |
| } |
| logger.info("Execution plan executed with {} entries", xplan.getTasks().size()); |
| } else { |
| logger.info("No tasks found in execution plan - no additional packages installed."); |
| } |
| |
| // save hashes to file for crosscheck on subsequent startup to avoid double processing |
| writer.write(String.valueOf(plan.hashCode())); |
| writer.newLine(); |
| |
| } |
| } finally { |
| session.logout(); |
| } |
| } finally { |
| st.close(); |
| } |
| } else { |
| logger.info("No execution plans configured - skipping init."); |
| } |
| } |
| } |