View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.catalog.git.dao;
18  
19  import java.io.File;
20  import java.io.FileWriter;
21  import java.io.IOException;
22  import java.time.Instant;
23  import java.time.LocalDateTime;
24  import java.time.ZoneId;
25  
26  import com.fasterxml.jackson.core.JsonFactory;
27  import com.fasterxml.jackson.core.JsonParser;
28  import com.fasterxml.jackson.databind.ObjectMapper;
29  import com.fasterxml.jackson.databind.SerializationFeature;
30  import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
31  import org.apache.logging.log4j.LogManager;
32  import org.apache.logging.log4j.Logger;
33  import org.apache.logging.log4j.catalog.api.CatalogData;
34  import org.apache.logging.log4j.catalog.api.dao.AbstractCatalogReader;
35  import org.apache.logging.log4j.catalog.api.dao.CatalogDao;
36  import org.apache.logging.log4j.catalog.api.exception.CatalogModificationException;
37  import org.apache.logging.log4j.catalog.api.exception.CatalogReadException;
38  import org.apache.logging.log4j.catalog.api.exception.CatalogNotFoundException;
39  import org.apache.logging.log4j.catalog.api.util.CatalogEventFilter;
40  import org.eclipse.jgit.api.CloneCommand;
41  import org.eclipse.jgit.api.Git;
42  import org.eclipse.jgit.api.PullCommand;
43  import org.eclipse.jgit.api.PushCommand;
44  import org.eclipse.jgit.api.TransportConfigCallback;
45  import org.eclipse.jgit.api.errors.GitAPIException;
46  import org.eclipse.jgit.internal.storage.file.FileRepository;
47  import org.eclipse.jgit.lib.Repository;
48  import org.eclipse.jgit.transport.CredentialsProvider;
49  
50  public class GitCatalogDao extends AbstractCatalogReader implements CatalogDao {
51      private static final Logger LOGGER = LogManager.getLogger();
52      private static final String DEFAULT_CATALOG_PATH = "src/main/resources/catalog.json";
53  
54      private final ObjectMapper mapper;
55  
56      private CredentialsProvider credentialsProvider = null;
57      private TransportConfigCallback transportConfigCallback = null;
58      private String remoteRepoUri = null;
59      private String localRepoPath = null;
60      private String catalogPath = DEFAULT_CATALOG_PATH;
61      private String branch = null;
62  
63      private Repository localRepo = null;
64      private Git git = null;
65      private File catalogFile = null;
66  
67      public GitCatalogDao() {
68          JsonFactory factory = new JsonFactory();
69          factory.enable(JsonParser.Feature.ALLOW_COMMENTS);
70          mapper = new ObjectMapper(factory).enable(SerializationFeature.INDENT_OUTPUT);
71          SimpleFilterProvider filterProvider = new SimpleFilterProvider();
72          filterProvider.addFilter("catalogEvent", new CatalogEventFilter());
73          mapper.setFilterProvider(filterProvider);
74      }
75  
76      public CredentialsProvider getCredentialsProvider() {
77          return credentialsProvider;
78      }
79  
80      public void setCredentialsProvider(CredentialsProvider credentialsProvider) {
81          this.credentialsProvider = credentialsProvider;
82      }
83  
84      public TransportConfigCallback getTransportConfigCallback() {
85          return transportConfigCallback;
86      }
87  
88      public void setTransportConfigCallback(TransportConfigCallback transportConfigCallback) {
89          this.transportConfigCallback = transportConfigCallback;
90      }
91  
92      public String getRemoteRepoUri() {
93          return remoteRepoUri;
94      }
95  
96      public void setRemoteRepoUri(String remoteRepoUri) {
97          this.remoteRepoUri = remoteRepoUri;
98      }
99  
100     public String getLocalRepoPath() {
101         return localRepoPath;
102     }
103 
104     public void setLocalRepoPath(String localRepoPath) {
105         this.localRepoPath = localRepoPath;
106     }
107 
108     public String getCatalogPath() {
109         return catalogPath;
110     }
111 
112     public void setCatalogPath(String catalogPath) {
113         this.catalogPath = catalogPath;
114     }
115 
116     public String getBranch() {
117         return branch;
118     }
119 
120     public void setBranch(String branch) {
121         this.branch = branch;
122     }
123 
124     @Override
125     public LocalDateTime getLastUpdated() {
126         if (localRepo == null) {
127             updateRepo();
128         }
129         return LocalDateTime.ofInstant(Instant.ofEpochMilli(catalogFile.lastModified()),
130                 ZoneId.systemDefault());
131     }
132 
133     @Override
134     public synchronized CatalogData read() {
135         updateRepo();
136         if (catalogFile == null || !catalogFile.exists() || !catalogFile.canRead()) {
137             throw new IllegalStateException("Catalog " + catalogFile.getAbsolutePath() + " is not readable.");
138         }
139 
140         try {
141             catalogData = mapper.readValue(catalogFile, CatalogData.class);
142             return catalogData;
143         } catch (IOException ioe) {
144             throw new CatalogReadException("Error reading catalog from " + catalogFile.getAbsolutePath());
145         }
146     }
147 
148     @Override
149     public void write(CatalogData data) {
150         File localRepoFile = new File(localRepoPath);
151         if (!localRepoFile.exists() || !localRepoFile.canWrite()) {
152             throw new IllegalStateException("Catalog is not writable.");
153         }
154 
155         FileWriter writer = null;
156         try {
157             String text = mapper.writeValueAsString(data);
158             writer = new FileWriter(catalogFile);
159             writer.write(text);
160         } catch (IOException ioException) {
161             throw new CatalogModificationException("Unable to write catalog file.", ioException);
162         } finally {
163             try { if (writer != null) writer.close(); } catch(Exception exception) { }
164         }
165 
166         try (Git git = Git.open(localRepoFile)) {
167             git.add().addFilepattern(catalogPath).call();
168             git.commit().setMessage("Catalog updated").call();
169             updateRepo();
170             PushCommand pushCommand = git.push();
171             if (credentialsProvider != null) {
172                 pushCommand.setCredentialsProvider(credentialsProvider);
173             }
174             if (transportConfigCallback != null) {
175                 pushCommand.setTransportConfigCallback(transportConfigCallback);
176             }
177             pushCommand.call();
178         } catch (GitAPIException | IOException ex) {
179             throw new CatalogModificationException("Unable to modify catalog", ex);
180         }
181     }
182 
183     @Override
184     public String readCatalog() {
185         return null;
186     }
187 
188     private void updateRepo() {
189 
190         File localRepoFile = new File(localRepoPath);
191         if (!localRepoFile.exists()) {
192             LOGGER.debug("local git repo {} does not exist - creating it", localRepoPath);
193             localRepoFile.getParentFile().mkdirs();
194             CloneCommand cloneCommand = Git.cloneRepository().setURI(remoteRepoUri).setDirectory(localRepoFile);
195             if (branch != null) {
196                 cloneCommand.setBranch(branch);
197             }
198             if (credentialsProvider != null) {
199                 cloneCommand.setCredentialsProvider(credentialsProvider);
200             }
201             if (transportConfigCallback != null) {
202                 cloneCommand.setTransportConfigCallback(transportConfigCallback);
203             }
204             try (Git git = cloneCommand.call()) {
205                 catalogFile = new File(localRepoFile, catalogPath);
206             } catch (Exception ex) {
207                 throw new CatalogNotFoundException("Unable to clone remote catalog at " + remoteRepoUri + " to " + localRepoPath, ex);
208             }
209         } else {
210             try {
211                 LOGGER.debug("local git repo {} exists - updating", localRepoPath);
212                 localRepo = new FileRepository(localRepoPath  + "/.git");
213                 catalogFile = new File(localRepoFile, catalogPath);
214                 git = new Git(localRepo);
215                 PullCommand pullCommand = git.pull();
216                 try {
217                     if (credentialsProvider != null) {
218                         pullCommand.setCredentialsProvider(credentialsProvider);
219                     }
220                     if (transportConfigCallback != null) {
221                         pullCommand.setTransportConfigCallback(transportConfigCallback);
222                     }
223                     pullCommand.call();
224                 } catch (GitAPIException gitApiException) {
225                     LOGGER.error("Exception", gitApiException);
226                 }
227             } catch (Exception exception) {
228                 throw new CatalogReadException("Unable to pull remote catalog at " + remoteRepoUri + " to " + localRepoPath, exception);
229             }
230         }
231     }
232 }