1 | package org.apache.maven.continuum.core.action; |
2 | |
3 | /* |
4 | * Licensed to the Apache Software Foundation (ASF) under one |
5 | * or more contributor license agreements. See the NOTICE file |
6 | * distributed with this work for additional information |
7 | * regarding copyright ownership. The ASF licenses this file |
8 | * to you under the Apache License, Version 2.0 (the |
9 | * "License"); you may not use this file except in compliance |
10 | * with the License. You may obtain a copy of the License at |
11 | * |
12 | * http://www.apache.org/licenses/LICENSE-2.0 |
13 | * |
14 | * Unless required by applicable law or agreed to in writing, |
15 | * software distributed under the License is distributed on an |
16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
17 | * KIND, either express or implied. See the License for the |
18 | * specific language governing permissions and limitations |
19 | * under the License. |
20 | */ |
21 | |
22 | import java.io.File; |
23 | import java.util.Date; |
24 | import java.util.Iterator; |
25 | import java.util.List; |
26 | import java.util.Map; |
27 | |
28 | import org.apache.continuum.dao.BuildResultDao; |
29 | import org.apache.continuum.dao.ProjectDao; |
30 | import org.apache.continuum.scm.ContinuumScm; |
31 | import org.apache.continuum.scm.ContinuumScmConfiguration; |
32 | import org.apache.continuum.utils.ContinuumUtils; |
33 | import org.apache.maven.continuum.model.project.BuildDefinition; |
34 | import org.apache.maven.continuum.model.project.BuildResult; |
35 | import org.apache.maven.continuum.model.project.Project; |
36 | import org.apache.maven.continuum.model.scm.ChangeFile; |
37 | import org.apache.maven.continuum.model.scm.ChangeSet; |
38 | import org.apache.maven.continuum.model.scm.ScmResult; |
39 | import org.apache.maven.continuum.notification.ContinuumNotificationDispatcher; |
40 | import org.apache.maven.continuum.project.ContinuumProjectState; |
41 | import org.apache.maven.continuum.store.ContinuumObjectNotFoundException; |
42 | import org.apache.maven.continuum.store.ContinuumStoreException; |
43 | import org.apache.maven.continuum.utils.WorkingDirectoryService; |
44 | import org.apache.maven.scm.ScmException; |
45 | import org.apache.maven.scm.ScmFile; |
46 | import org.apache.maven.scm.command.update.UpdateScmResult; |
47 | import org.apache.maven.scm.manager.NoSuchScmProviderException; |
48 | import org.apache.maven.scm.repository.ScmRepositoryException; |
49 | |
50 | /** |
51 | * @author <a href="mailto:trygvis@inamo.no">Trygve Laugstøl</a> |
52 | * @version $Id: UpdateWorkingDirectoryFromScmContinuumAction.java 800655 2009-08-04 02:01:14Z ctan $ |
53 | * @plexus.component role="org.codehaus.plexus.action.Action" role-hint="update-working-directory-from-scm" |
54 | */ |
55 | public class UpdateWorkingDirectoryFromScmContinuumAction |
56 | extends AbstractContinuumAction |
57 | { |
58 | private static final String KEY_UPDATE_SCM_RESULT = "update-result"; |
59 | |
60 | /** |
61 | * @plexus.requirement |
62 | */ |
63 | private ContinuumNotificationDispatcher notifier; |
64 | |
65 | /** |
66 | * @plexus.requirement |
67 | */ |
68 | private ContinuumScm scm; |
69 | |
70 | /** |
71 | * @plexus.requirement |
72 | */ |
73 | private WorkingDirectoryService workingDirectoryService; |
74 | |
75 | /** |
76 | * @plexus.requirement |
77 | */ |
78 | private BuildResultDao buildResultDao; |
79 | |
80 | /** |
81 | * @plexus.requirement |
82 | */ |
83 | private ProjectDao projectDao; |
84 | |
85 | public void execute( Map context ) |
86 | throws ScmRepositoryException, NoSuchScmProviderException, ScmException, ContinuumObjectNotFoundException, |
87 | ContinuumStoreException |
88 | { |
89 | Project project = projectDao.getProject( getProject( context ).getId() ); |
90 | |
91 | BuildDefinition buildDefinition = getBuildDefinition( context ); |
92 | |
93 | UpdateScmResult scmResult; |
94 | |
95 | ScmResult result; |
96 | |
97 | Date latestUpdateDate = null; |
98 | |
99 | int originalState = project.getState(); |
100 | |
101 | project.setState( ContinuumProjectState.UPDATING ); |
102 | |
103 | projectDao.updateProject( project ); |
104 | |
105 | try |
106 | { |
107 | BuildResult buildResult = buildResultDao.getLatestBuildResultForProject( project.getId() ); |
108 | |
109 | latestUpdateDate = new Date( buildResult.getStartTime() ); |
110 | } |
111 | catch ( Exception e ) |
112 | { |
113 | } |
114 | |
115 | try |
116 | { |
117 | notifier.checkoutStarted( project, buildDefinition ); |
118 | |
119 | // TODO: not sure why this is different to the context, but it all needs to change |
120 | File workingDirectory = workingDirectoryService.getWorkingDirectory( project ); |
121 | ContinuumScmConfiguration config = createScmConfiguration( project, workingDirectory ); |
122 | config.setLatestUpdateDate( latestUpdateDate ); |
123 | String tag = config.getTag(); |
124 | String msg = |
125 | project.getName() + "', id: '" + project.getId() + "' to '" + workingDirectory.getAbsolutePath() + "'" + |
126 | ( tag != null ? " with branch/tag " + tag + "." : "." ); |
127 | getLogger().info( "Updating project: " + msg ); |
128 | scmResult = scm.update( config ); |
129 | |
130 | if ( !scmResult.isSuccess() ) |
131 | { |
132 | getLogger().warn( "Error while updating the code for project: '" + msg ); |
133 | |
134 | getLogger().warn( "Command output: " + scmResult.getCommandOutput() ); |
135 | |
136 | getLogger().warn( "Provider message: " + scmResult.getProviderMessage() ); |
137 | } |
138 | |
139 | if ( scmResult.getUpdatedFiles() != null && scmResult.getUpdatedFiles().size() > 0 ) |
140 | { |
141 | getLogger().info( "Updated " + scmResult.getUpdatedFiles().size() + " files." ); |
142 | } |
143 | |
144 | result = convertScmResult( scmResult ); |
145 | } |
146 | catch ( ScmRepositoryException e ) |
147 | { |
148 | result = new ScmResult(); |
149 | |
150 | result.setSuccess( false ); |
151 | |
152 | result.setProviderMessage( e.getMessage() + ": " + getValidationMessages( e ) ); |
153 | |
154 | getLogger().error( e.getMessage(), e ); |
155 | } |
156 | catch ( NoSuchScmProviderException e ) |
157 | { |
158 | // TODO: this is not making it back into a result of any kind - log it at least. Same is probably the case for ScmException |
159 | result = new ScmResult(); |
160 | |
161 | result.setSuccess( false ); |
162 | |
163 | result.setProviderMessage( e.getMessage() ); |
164 | |
165 | getLogger().error( e.getMessage(), e ); |
166 | } |
167 | catch ( ScmException e ) |
168 | { |
169 | result = new ScmResult(); |
170 | |
171 | result.setSuccess( false ); |
172 | |
173 | result.setException( ContinuumUtils.throwableMessagesToString( e ) ); |
174 | |
175 | getLogger().error( e.getMessage(), e ); |
176 | } |
177 | finally |
178 | { |
179 | // set back to the original state |
180 | try |
181 | { |
182 | project = projectDao.getProject( project.getId() ); |
183 | |
184 | project.setState( originalState ); |
185 | |
186 | projectDao.updateProject( project ); |
187 | } |
188 | catch ( Exception e ) |
189 | { |
190 | // nasty nasty, but we're in finally, so just sacrifice the state to keep the original exception |
191 | getLogger().error( e.getMessage(), e ); |
192 | } |
193 | |
194 | notifier.checkoutComplete( project, buildDefinition ); |
195 | } |
196 | |
197 | context.put( KEY_UPDATE_SCM_RESULT, result ); |
198 | AbstractContinuumAction.setProject( context, project ); |
199 | } |
200 | |
201 | private ContinuumScmConfiguration createScmConfiguration( Project project, File workingDirectory ) |
202 | { |
203 | ContinuumScmConfiguration config = new ContinuumScmConfiguration(); |
204 | config.setUrl( project.getScmUrl() ); |
205 | config.setUsername( project.getScmUsername() ); |
206 | config.setPassword( project.getScmPassword() ); |
207 | config.setUseCredentialsCache( project.isScmUseCache() ); |
208 | config.setWorkingDirectory( workingDirectory ); |
209 | config.setTag( project.getScmTag() ); |
210 | return config; |
211 | } |
212 | |
213 | private ScmResult convertScmResult( UpdateScmResult scmResult ) |
214 | { |
215 | ScmResult result = new ScmResult(); |
216 | |
217 | result.setCommandLine( maskPassword( scmResult.getCommandLine() ) ); |
218 | |
219 | result.setSuccess( scmResult.isSuccess() ); |
220 | |
221 | result.setCommandOutput( scmResult.getCommandOutput() ); |
222 | |
223 | result.setProviderMessage( scmResult.getProviderMessage() ); |
224 | |
225 | if ( scmResult.getChanges() != null && !scmResult.getChanges().isEmpty() ) |
226 | { |
227 | for ( org.apache.maven.scm.ChangeSet scmChangeSet : (List<org.apache.maven.scm.ChangeSet>) scmResult.getChanges() ) |
228 | { |
229 | ChangeSet change = new ChangeSet(); |
230 | |
231 | change.setAuthor( scmChangeSet.getAuthor() ); |
232 | |
233 | change.setComment( scmChangeSet.getComment() ); |
234 | |
235 | if ( scmChangeSet.getDate() != null ) |
236 | { |
237 | change.setDate( scmChangeSet.getDate().getTime() ); |
238 | } |
239 | |
240 | if ( scmChangeSet.getFiles() != null ) |
241 | { |
242 | for ( org.apache.maven.scm.ChangeFile f : (List<org.apache.maven.scm.ChangeFile>) scmChangeSet.getFiles() ) |
243 | { |
244 | ChangeFile file = new ChangeFile(); |
245 | |
246 | file.setName( f.getName() ); |
247 | |
248 | file.setRevision( f.getRevision() ); |
249 | |
250 | change.addFile( file ); |
251 | } |
252 | } |
253 | |
254 | result.addChange( change ); |
255 | } |
256 | } |
257 | else |
258 | { |
259 | // We don't have a changes information probably because provider doesn't have a changelog command |
260 | // so we use the updated list that contains only the updated files list |
261 | ChangeSet changeSet = convertScmFileSetToChangeSet( scmResult.getUpdatedFiles() ); |
262 | |
263 | if ( changeSet != null ) |
264 | { |
265 | result.addChange( changeSet ); |
266 | } |
267 | |
268 | } |
269 | |
270 | return result; |
271 | } |
272 | |
273 | private static ChangeSet convertScmFileSetToChangeSet( List<ScmFile> files ) |
274 | { |
275 | ChangeSet changeSet = null; |
276 | |
277 | if ( files != null && !files.isEmpty() ) |
278 | { |
279 | changeSet = new ChangeSet(); |
280 | |
281 | // TODO: author, etc. |
282 | for ( ScmFile scmFile : files ) |
283 | { |
284 | ChangeFile file = new ChangeFile(); |
285 | |
286 | file.setName( scmFile.getPath() ); |
287 | |
288 | // TODO: revision? |
289 | |
290 | file.setStatus( scmFile.getStatus().toString() ); |
291 | |
292 | changeSet.addFile( file ); |
293 | } |
294 | } |
295 | return changeSet; |
296 | } |
297 | |
298 | // TODO: migrate to the SvnCommandLineUtils version (preferably properly encapsulated in the provider) |
299 | private String maskPassword( String commandLine ) |
300 | { |
301 | String cmd = commandLine; |
302 | |
303 | if ( cmd != null && cmd.startsWith( "svn" ) ) |
304 | { |
305 | String pwdString = "--password"; |
306 | |
307 | if ( cmd.indexOf( pwdString ) > 0 ) |
308 | { |
309 | int index = cmd.indexOf( pwdString ) + pwdString.length() + 1; |
310 | |
311 | int nextSpace = cmd.indexOf( " ", index ); |
312 | |
313 | cmd = cmd.substring( 0, index ) + "********" + cmd.substring( nextSpace ); |
314 | } |
315 | } |
316 | |
317 | return cmd; |
318 | } |
319 | |
320 | private String getValidationMessages( ScmRepositoryException ex ) |
321 | { |
322 | List<String> messages = ex.getValidationMessages(); |
323 | |
324 | StringBuffer message = new StringBuffer(); |
325 | |
326 | if ( messages != null && !messages.isEmpty() ) |
327 | { |
328 | for ( Iterator<String> i = messages.iterator(); i.hasNext(); ) |
329 | { |
330 | message.append( i.next() ); |
331 | |
332 | if ( i.hasNext() ) |
333 | { |
334 | message.append( System.getProperty( "line.separator" ) ); |
335 | } |
336 | } |
337 | } |
338 | return message.toString(); |
339 | } |
340 | |
341 | public static ScmResult getUpdateScmResult( Map<String, Object> context ) |
342 | { |
343 | return getUpdateScmResult( context, null ); |
344 | } |
345 | |
346 | public static ScmResult getUpdateScmResult( Map<String, Object> context, ScmResult defaultValue ) |
347 | { |
348 | return (ScmResult) getObject( context, KEY_UPDATE_SCM_RESULT, defaultValue ); |
349 | } |
350 | } |