001    /**
002     * Copyright 2010-2013 The Kuali Foundation
003     *
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.opensource.org/licenses/ecl2.php
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.common.util;
017    
018    import java.io.File;
019    import java.util.ArrayList;
020    import java.util.Arrays;
021    import java.util.HashMap;
022    import java.util.List;
023    import java.util.Map;
024    import java.util.Properties;
025    import java.util.Set;
026    
027    import org.apache.commons.lang3.StringUtils;
028    import org.kuali.common.util.property.Constants;
029    import org.kuali.common.util.property.PropertiesContext;
030    import org.slf4j.Logger;
031    import org.slf4j.LoggerFactory;
032    import org.springframework.util.PropertyPlaceholderHelper;
033    
034    /**
035     * 
036     */
037    public class ProjectUtils {
038    
039            private static final Logger logger = LoggerFactory.getLogger(ProjectUtils.class);
040            private static final PropertyPlaceholderHelper PPH = Constants.DEFAULT_PROPERTY_PLACEHOLDER_HELPER;
041            private static final String GROUP_ID_BASE_PATH_KEY = "project.groupId.base.path";
042            private static final String CLASSPATH = "classpath:";
043    
044            @Deprecated
045            public static final String KUALI_COMMON_GROUP_ID = KualiProjectConstants.COMMON_GROUP_ID;
046    
047            @Deprecated
048            public static final String KUALI_UTIL_ARTIFACT_ID = UtilProjectContext.ARTIFACT_ID;
049    
050            private static final Map<String, Properties> PROJECT_PROPERTIES_CACHE = new HashMap<String, Properties>();
051    
052            public static List<Project> loadProjects(List<String> projectIds) {
053                    List<Project> projects = new ArrayList<Project>();
054                    for (String projectId : projectIds) {
055                            Project project = ProjectUtils.loadProject(projectId);
056                            projects.add(project);
057                    }
058                    return projects;
059            }
060    
061            /**
062             * <pre>
063             *   kuali-util = classpath:org/kuali/common/kuali-util
064             * </pre>
065             */
066            public static String getCommonClassPathPrefix(String artifactId) {
067                    return getClassPathPrefix(KualiProjectConstants.COMMON_GROUP_ID, artifactId);
068            }
069    
070            /**
071             * Given a groupId and artifactId, convert the groupId to groupId.base, then return the classpath prefix
072             * 
073             * <pre>
074             *   org.kuali.student.db:ks-impex-rice-db = classpath:org/kuali/student/ks-impex-rice-db
075             *   org.kuali.common:kuali-util           = classpath:org/kuali/common/kuali-util
076             * </pre>
077             */
078            public static String getClassPathPrefix(String groupId, String artifactId) {
079                    Project project = loadProject(groupId, artifactId);
080                    return CLASSPATH + getResourcePath(project);
081            }
082    
083            /**
084             * Given groupId:artifactId, convert the groupId to groupId.base, then return the classpath prefix
085             * 
086             * <pre>
087             *   org.kuali.student.db:ks-impex-rice-db = classpath:org/kuali/student/ks-impex-rice-db
088             *   org.kuali.common:kuali-util           = classpath:org/kuali/common/kuali-util
089             * </pre>
090             * 
091             * Use getClassPathPrefixFromProjectId() instead
092             */
093            @Deprecated
094            public static String getClassPathPrefixFromGAV(String projectId) {
095                    Project project = getProject(projectId);
096                    return getClassPathPrefix(project);
097            }
098    
099            /**
100             * Given groupId:artifactId, convert the groupId to groupId.base, then return the classpath prefix
101             * 
102             * <pre>
103             *   org.kuali.student.db:ks-impex-rice-db = classpath:org/kuali/student/ks-impex-rice-db
104             *   org.kuali.common:kuali-util           = classpath:org/kuali/common/kuali-util
105             * </pre>
106             */
107            public static String getClassPathPrefixFromProjectId(String projectId) {
108                    Project project = getProject(projectId);
109                    return getClassPathPrefix(project);
110            }
111    
112            /**
113             * Given a project containing groupId + artifactId, convert the groupId to groupId.base, then return the classpath prefix
114             * 
115             * <pre>
116             *   org.kuali.student.db:ks-impex-rice-db = classpath:org/kuali/student/ks-impex-rice-db
117             *   org.kuali.common:kuali-util           = classpath:org/kuali/common/kuali-util
118             * </pre>
119             */
120            public static String getClassPathPrefix(Project project) {
121                    return getClassPathPrefix(project.getGroupId(), project.getArtifactId());
122            }
123    
124            /**
125             * Given a groupId and artifactId, convert the groupId to groupId.base, then return a resource path relative to directory
126             * 
127             * <pre>
128             *   org.kuali.student.db:ks-impex-rice-db    = org/kuali/student/ks-impex-rice-db
129             *   org.kuali.common:kuali-util              = org/kuali/common/kuali-util
130             *   
131             *   /tmp/x/y/z + org.kuali.common:kuali-util = /tmp/x/y/z/org/kuali/common/kuali-util
132             * </pre>
133             */
134            public static File getResourceDirectory(File directory, Project project) {
135                    String resourcePath = getResourcePath(project);
136                    File file = new File(directory, resourcePath);
137                    return new File(LocationUtils.getCanonicalPath(file));
138            }
139    
140            /**
141             * Given a groupId and artifactId, convert the groupId to groupId.base, then return a handle to a file relative to directory with the given filename
142             * 
143             * <pre>
144             *   org.kuali.student.db:ks-impex-rice-db              = org/kuali/student/ks-impex-rice-db
145             *   org.kuali.common:kuali-util                        = org/kuali/common/kuali-util
146             *   
147             *   /tmp/x/y/z + org.kuali.common:kuali-util + foo.txt = /tmp/x/y/z/org/kuali/common/kuali-util/foo.txt
148             * </pre>
149             */
150            public static File getResourceFile(File directory, Project project, String filename) {
151                    File dir = getResourceDirectory(directory, project);
152                    return new File(dir, filename);
153            }
154    
155            /**
156             * Given groupId:artifactId, convert the groupId to groupId.base, then return a resource friendly prefix
157             * 
158             * <pre>
159             *   org.kuali.student.db:ks-impex-rice-db = org/kuali/student/ks-impex-rice-db
160             *   org.kuali.common:kuali-util           = org/kuali/common/kuali-util
161             * </pre>
162             */
163            public static String getResourcePath(Project project) {
164                    Properties properties = project.getProperties();
165                    String groupIdPath = properties.getProperty(GROUP_ID_BASE_PATH_KEY);
166                    Assert.hasText(groupIdPath, "groupIdPath has no text");
167                    String artifactId = project.getArtifactId();
168                    return groupIdPath + "/" + artifactId;
169            }
170    
171            /**
172             * 
173             */
174            @Deprecated
175            public static org.kuali.common.util.property.ProjectProperties getProjectProperties(ProjectContext context) {
176    
177                    // Get a project object based on the context information
178                    Project project = loadProject(context);
179    
180                    // Create a properties context object from the project.properties file from META-INF
181                    PropertiesContext propertiesContext = new PropertiesContext(project.getProperties());
182                    propertiesContext.setEncoding(project.getEncoding());
183                    propertiesContext.setLocations(context.getPropertyLocations());
184    
185                    // Return a project properties object
186                    return new org.kuali.common.util.property.ProjectProperties(project, propertiesContext);
187            }
188    
189            /**
190             * Create a <code>Project</code> object from the <code>context</code>. This includes loading the corresponding <code>project.properties</code> file from disk.
191             */
192            @Deprecated
193            public static Project loadProject(ProjectContext context) {
194                    return loadProject(getGav(context));
195            }
196    
197            @Deprecated
198            public static String getGav(ProjectContext context) {
199                    return getGav(context.getGroupId(), context.getArtifactId());
200            }
201    
202            @Deprecated
203            public static String getGav(Project project) {
204                    return getGav(project.getGroupId(), project.getArtifactId());
205            }
206    
207            @Deprecated
208            public static String getGav(String groupId, String artifactId) {
209                    return groupId + ":" + artifactId;
210            }
211    
212            public static String getProjectId(Project project) {
213                    return getProjectId(project.getGroupId(), project.getArtifactId());
214            }
215    
216            public static String getProjectId(String groupId, String artifactId) {
217                    return groupId + ":" + artifactId;
218            }
219    
220            /**
221             * Create a <code>Project</code> object from <code>groupId</code>, <code>artifactId</code> pair. This includes loading the corresponding <code>project.properties</code> file
222             * from disk.
223             */
224            public static Project loadProject(String groupId, String artifactId) {
225                    String projectId = getProjectId(groupId, artifactId);
226                    return loadProject(projectId);
227            }
228    
229            /**
230             * Create a <code>Project</code> object from the <code>projectId</code>. This includes loading the corresponding <code>project.properties</code> file from disk.
231             */
232            public static Project loadProject(String projectId) {
233                    // Convert the projectId into a Project
234                    Project project = getProject(projectId);
235    
236                    // Load properties from a .properties file for this project
237                    Properties properties = loadProperties(project);
238    
239                    // Return a fully configured project object based on the properties
240                    Project loadedProject = getProject(properties);
241    
242                    // return the project we loaded
243                    return loadedProject;
244            }
245    
246            /**
247             * Provide a way to clear the cache
248             */
249            public synchronized static void clearCache() {
250                    PROJECT_PROPERTIES_CACHE.clear();
251            }
252    
253            /**
254             * Create a skeleton <code>Project</code> object from the <code>gav</code>. Nothing but the GAV info (groupId:artifactId:packaging:version:classifier) gets filled in. Does not
255             * read <code>project.properties</code> from disk.
256             */
257            public static Project getProject(String gav) {
258                    logger.debug("Processing [{}]", gav);
259                    String[] tokens = StringUtils.split(gav, ":");
260    
261                    Project project = new Project();
262                    if (tokens.length > 0) {
263                            project.setGroupId(RepositoryUtils.toNull(tokens[0]));
264                    }
265                    if (tokens.length > 1) {
266                            project.setArtifactId(RepositoryUtils.toNull(tokens[1]));
267                    }
268                    if (tokens.length > 2) {
269                            project.setPackaging(RepositoryUtils.toNull(tokens[2]));
270                    }
271                    if (tokens.length > 3) {
272                            project.setVersion(RepositoryUtils.toNull(tokens[3]));
273                    }
274                    if (tokens.length > 4) {
275                            project.setClassifier(RepositoryUtils.toNull(tokens[4]));
276                    }
277                    return project;
278            }
279    
280            public static List<Dependency> getDependencies(String csv) {
281                    List<String> tokens = CollectionUtils.getTrimmedListFromCSV(csv);
282                    List<Dependency> dependencies = new ArrayList<Dependency>();
283                    for (String token : tokens) {
284                            Dependency dependency = RepositoryUtils.parseDependency(token);
285                            dependencies.add(dependency);
286                    }
287                    return dependencies;
288            }
289    
290            /**
291             * Return a <code>Project</code> object by copying values from the <code>properties</code> object into a <code>Project</code> object.
292             */
293            public static Project getProject(Properties properties) {
294                    List<String> skipKeys = Arrays.asList("project.dependencies");
295                    String startsWith = "project.";
296                    List<String> keys = PropertyUtils.getStartsWithKeys(properties, startsWith);
297                    Project project = new Project();
298                    project.setProperties(properties);
299                    Map<String, Object> description = ReflectionUtils.describe(project);
300                    Set<String> beanProperties = description.keySet();
301                    for (String key : keys) {
302                            if (skipKeys.contains(key)) {
303                                    continue;
304                            }
305                            String value = properties.getProperty(key);
306                            String beanProperty = getBeanProperty(key, startsWith);
307                            if (beanProperties.contains(beanProperty)) {
308                                    ReflectionUtils.copyProperty(project, beanProperty, value);
309                            }
310                    }
311                    String csv = RepositoryUtils.toNull(properties.getProperty("project.dependencies"));
312                    List<Dependency> dependencies = getDependencies(csv);
313                    project.setDependencies(dependencies);
314                    return project;
315            }
316    
317            protected static String getBeanProperty(String key, String startsWith) {
318                    String s = StringUtils.substring(key, startsWith.length());
319                    String[] tokens = StringUtils.split(s, ".");
320                    StringBuilder sb = new StringBuilder();
321                    for (int i = 0; i < tokens.length; i++) {
322                            String token = tokens[i];
323                            if (i == 0) {
324                                    sb.append(token);
325                            } else {
326                                    sb.append(StringUtils.capitalize(token));
327                            }
328                    }
329                    return sb.toString();
330            }
331    
332            public static Properties loadProperties(String gav) {
333                    return loadProperties(getProject(gav));
334            }
335    
336            /**
337             * Use the groupId and artifactId from this project to load the corresponding project.properties file and cache it in our internal Map
338             */
339            public static synchronized Properties loadProperties(Project project) {
340                    String projectId = getProjectId(project.getGroupId(), project.getArtifactId());
341                    Properties properties = PROJECT_PROPERTIES_CACHE.get(projectId);
342                    if (properties == null) {
343                            properties = loadAndCache(project, projectId);
344                    }
345                    return properties;
346            }
347    
348            protected static Properties loadAndCache(Project project, String projectId) {
349                    String location = getPropertiesFileLocation(project);
350    
351                    // If it doesn't exist, we've got issues
352                    Assert.exists(location);
353    
354                    Properties properties = PropertyUtils.load(location);
355                    PROJECT_PROPERTIES_CACHE.put(projectId, properties);
356                    return properties;
357            }
358    
359            public static String getPropertiesFileLocation(Project project) {
360                    Assert.hasText(project.getGroupId(), "groupId has no text");
361                    Assert.hasText(project.getArtifactId(), "artifactId has no text");
362    
363                    Properties properties = new Properties();
364                    properties.setProperty(Constants.GROUP_ID_BASE_PATH_KEY, Str.getPath(project.getGroupId()));
365                    properties.setProperty(Constants.ARTIFACT_ID_KEY, project.getArtifactId());
366    
367                    return PPH.replacePlaceholders(Constants.PROJECT_PROPERTIES_LOCATION, properties);
368            }
369    
370    }