View Javadoc
1   /*
2    * #%L
3    * wcm.io
4    * %%
5    * Copyright (C) 2015 wcm.io
6    * %%
7    * Licensed under the Apache License, Version 2.0 (the "License");
8    * you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   *
11   *      http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   * #L%
19   */
20  package io.wcm.dam.assetservice.impl;
21  
22  import java.util.Collections;
23  import java.util.HashMap;
24  import java.util.HashSet;
25  import java.util.Iterator;
26  import java.util.Map;
27  import java.util.Set;
28  import java.util.concurrent.Executors;
29  import java.util.concurrent.ScheduledExecutorService;
30  import java.util.regex.Pattern;
31  
32  import org.apache.commons.lang3.StringUtils;
33  import org.apache.sling.api.resource.ResourceResolverFactory;
34  import org.slf4j.Logger;
35  import org.slf4j.LoggerFactory;
36  
37  import com.day.cq.dam.api.DamConstants;
38  import com.day.cq.dam.api.DamEvent;
39  
40  import io.wcm.dam.assetservice.impl.dataversion.ChecksumDataVersionStrategy;
41  import io.wcm.dam.assetservice.impl.dataversion.DataVersionStrategy;
42  import io.wcm.dam.assetservice.impl.dataversion.TimestampDataVersionStrategy;
43  
44  /**
45   * Handles list of configured DAM paths and listens to DAM events on this paths to generate
46   * a new data version on each DAM content change relevant for the DAM asset services consumers.
47   * Make sure you call the shutdown method when the instance is no longer needed.
48   */
49  public class DamPathHandler {
50  
51    /**
52     * Full DAM path is used if not DAM path is given in configuration.
53     */
54    private static final String DEFAULT_DAM_PATH = DamConstants.MOUNTPOINT_ASSETS;
55  
56    private final Set<String> damPaths;
57    private final Pattern damPathsPattern;
58    private final ScheduledExecutorService executor;
59    private final Map<String, DataVersionStrategy> dataVersionStrategies;
60  
61    private static final Logger log = LoggerFactory.getLogger(DamPathHandler.class);
62  
63    /**
64     * @param configuredDamPaths Configured DAM paths
65     * @param dataVersionStrategyId Data version strategy
66     * @param dataVersionUpdateIntervalSec Update interface
67     * @param resourceResolverFactory Resource resolver factory
68     */
69    public DamPathHandler(final String[] configuredDamPaths,
70        String dataVersionStrategyId,
71        int dataVersionUpdateIntervalSec,
72        ResourceResolverFactory resourceResolverFactory) {
73  
74  
75      this.damPaths = validateDamPaths(configuredDamPaths);
76      this.damPathsPattern = buildDamPathsPattern(this.damPaths);
77  
78      log.debug("Start executor for DamPathHandler");
79      this.executor = Executors.newSingleThreadScheduledExecutor();
80  
81      dataVersionStrategies = new HashMap<>();
82      for (String damPath : this.damPaths) {
83        DataVersionStrategy dataVersionStrategy = getDataVersionStrategy(damPath, dataVersionStrategyId,
84            dataVersionUpdateIntervalSec, resourceResolverFactory, this.executor);
85        dataVersionStrategies.put(damPath, dataVersionStrategy);
86      }
87    }
88  
89    private static DataVersionStrategy getDataVersionStrategy(String damPath, String dataVersionStrategyId,
90        int dataVersionUpdateIntervalSec, ResourceResolverFactory resourceResolverFactory,
91        ScheduledExecutorService executor) {
92      if (StringUtils.equals(dataVersionStrategyId, TimestampDataVersionStrategy.STRATEGY)) {
93        return new TimestampDataVersionStrategy(damPath);
94      }
95      if (StringUtils.equals(dataVersionStrategyId, ChecksumDataVersionStrategy.STRATEGY)) {
96        return new ChecksumDataVersionStrategy(damPath, dataVersionUpdateIntervalSec, resourceResolverFactory, executor);
97      }
98      throw new IllegalArgumentException("Invalid data version strategy: " + dataVersionStrategyId);
99    }
100 
101   /**
102    * Shuts down the executor service.
103    */
104   public void shutdown() {
105     log.debug("Shutdown executor for DamPathHandler");
106     this.executor.shutdownNow();
107   }
108 
109   private static Set<String> validateDamPaths(String[] damPaths) {
110     Set<String> paths = new HashSet<>();
111     if (damPaths != null) {
112       for (String path : damPaths) {
113         if (StringUtils.isNotBlank(path)) {
114           paths.add(path);
115         }
116       }
117     }
118     if (paths.isEmpty()) {
119       paths.add(DEFAULT_DAM_PATH);
120     }
121     return Collections.unmodifiableSet(paths);
122   }
123 
124   /**
125    * Set DAM paths that should be handled. Only called once by {@link AssetRequestServlet}.
126    * @param damPaths DAM folder paths or empty/null if all should be handled.
127    * @return Regex pattern to match content paths
128    */
129   private static Pattern buildDamPathsPattern(Set<String> damPaths) {
130     StringBuilder pattern = new StringBuilder();
131     pattern.append("^(");
132     Iterator<String> paths = damPaths.iterator();
133     while (paths.hasNext()) {
134       pattern.append(Pattern.quote(paths.next()));
135       pattern.append("/.*");
136       if (paths.hasNext()) {
137         pattern.append("|");
138       }
139     }
140     pattern.append(")$");
141     return Pattern.compile(pattern.toString());
142   }
143 
144   /**
145    * Checks if the given DAM asset is allowed to process.
146    * @param assetPath Asset path
147    * @return true if processing is allowed.
148    */
149   public boolean isAllowedAssetPath(String assetPath) {
150     if (assetPath == null) {
151       return false;
152     }
153     return damPathsPattern.matcher(assetPath).matches();
154   }
155 
156   /**
157    * Checks if the given folder path is allowed to get a data version.
158    * @param path DAM folder path
159    * @return true if getting data version is allowed for this path.
160    */
161   public boolean isAllowedDataVersionPath(String path) {
162     return damPaths.contains(path);
163   }
164 
165   /**
166    * Get current data version for all allowed assets.
167    * @return Data version
168    */
169   public String getDataVersion(String damPath) {
170     DataVersionStrategy dataVersionStrategy = this.dataVersionStrategies.get(damPath);
171     if (dataVersionStrategy != null) {
172       return dataVersionStrategy.getDataVersion();
173     }
174     else {
175       return null;
176     }
177   }
178 
179   /**
180    * Handle DAM event.
181    * @param event DAM event
182    */
183   public void handleDamEvent(DamEvent event) {
184     if (isAllowedAssetPath(event.getAssetPath())) {
185       // route event to matching data version strategy instance
186       DataVersionStrategy dataVersionStrategy = getMatchingDataVersionStrategy(event.getAssetPath());
187       if (dataVersionStrategy != null) {
188         dataVersionStrategy.handleDamEvent(event);
189       }
190     }
191   }
192 
193   private DataVersionStrategy getMatchingDataVersionStrategy(String path) {
194     // shortcut if there is only one path configured
195     if (dataVersionStrategies.size() == 1) {
196       return dataVersionStrategies.values().iterator().next();
197     }
198     // find matching strategy for path
199     for (DataVersionStrategy dataVersionStrategy : this.dataVersionStrategies.values()) {
200       if (dataVersionStrategy.matches(path)) {
201         return dataVersionStrategy;
202       }
203     }
204     return null;
205   }
206 
207 }