001 /*
002 * Sonar, open source software quality management tool.
003 * Copyright (C) 2008-2011 SonarSource
004 * mailto:contact AT sonarsource DOT com
005 *
006 * Sonar is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 3 of the License, or (at your option) any later version.
010 *
011 * Sonar is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 * Lesser General Public License for more details.
015 *
016 * You should have received a copy of the GNU Lesser General Public
017 * License along with Sonar; if not, write to the Free Software
018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
019 */
020 package org.sonar.core.resource;
021
022 import org.apache.commons.lang.ArrayUtils;
023 import org.apache.commons.lang.StringUtils;
024 import org.apache.ibatis.session.ExecutorType;
025 import org.apache.ibatis.session.ResultContext;
026 import org.apache.ibatis.session.ResultHandler;
027 import org.apache.ibatis.session.SqlSession;
028 import org.sonar.api.resources.Qualifiers;
029 import org.sonar.api.resources.Scopes;
030 import org.sonar.core.persistence.MyBatis;
031
032 public class ResourceIndexerDao {
033
034 public static final int MINIMUM_KEY_SIZE = 3;
035
036 // The scopes and qualifiers that are not in the following constants are not indexed at all.
037 // Directories and packages are explicitly excluded.
038 private static final String[] RENAMABLE_QUALIFIERS = {Qualifiers.PROJECT, Qualifiers.MODULE, Qualifiers.VIEW, Qualifiers.SUBVIEW};
039 private static final String[] RENAMABLE_SCOPES = {Scopes.PROJECT};
040 private static final String[] NOT_RENAMABLE_QUALIFIERS = {Qualifiers.FILE, Qualifiers.UNIT_TEST_FILE, Qualifiers.CLASS};
041 private static final String[] NOT_RENAMABLE_SCOPES = {Scopes.FILE};
042
043 private final MyBatis mybatis;
044
045 public ResourceIndexerDao(MyBatis mybatis) {
046 this.mybatis = mybatis;
047 }
048
049 /**
050 * This method is reentrant. It can be executed even if the project is already indexed.
051 */
052 public ResourceIndexerDao indexProject(final int rootProjectId) {
053 SqlSession session = mybatis.openSession(ExecutorType.BATCH);
054 try {
055 ResourceIndexerMapper mapper = session.getMapper(ResourceIndexerMapper.class);
056 doIndexProject(rootProjectId, session, mapper);
057 session.commit();
058 return this;
059
060 } finally {
061 session.close();
062 }
063 }
064
065 /**
066 * This method is reentrant. It can be executed even if some projects are already indexed.
067 */
068 public ResourceIndexerDao indexProjects() {
069 final SqlSession session = mybatis.openSession(ExecutorType.BATCH);
070 try {
071 final ResourceIndexerMapper mapper = session.getMapper(ResourceIndexerMapper.class);
072 session.select("selectRootProjectIds", /* workaround to get booleans */ResourceIndexerQuery.create(), new ResultHandler() {
073 public void handleResult(ResultContext context) {
074 Integer rootProjectId = (Integer) context.getResultObject();
075 doIndexProject(rootProjectId, session, mapper);
076 session.commit();
077 }
078 });
079 return this;
080
081 } finally {
082 session.close();
083 }
084 }
085
086 private void doIndexProject(int rootProjectId, SqlSession session, final ResourceIndexerMapper mapper) {
087 // non indexed resources
088 ResourceIndexerQuery query = ResourceIndexerQuery.create()
089 .setNonIndexedOnly(true)
090 .setQualifiers(NOT_RENAMABLE_QUALIFIERS)
091 .setScopes(NOT_RENAMABLE_SCOPES)
092 .setRootProjectId(rootProjectId);
093
094 session.select("selectResources", query, new ResultHandler() {
095 public void handleResult(ResultContext context) {
096 ResourceDto resource = (ResourceDto) context.getResultObject();
097 doIndex(resource, mapper);
098 }
099 });
100
101 // some resources can be renamed, so index must be regenerated
102 // -> delete existing rows and create them again
103 query = ResourceIndexerQuery.create()
104 .setNonIndexedOnly(false)
105 .setQualifiers(RENAMABLE_QUALIFIERS)
106 .setScopes(RENAMABLE_SCOPES)
107 .setRootProjectId(rootProjectId);
108
109 session.select("selectResources", query, new ResultHandler() {
110 public void handleResult(ResultContext context) {
111 ResourceDto resource = (ResourceDto) context.getResultObject();
112
113 mapper.deleteByResourceId(resource.getId());
114 doIndex(resource, mapper);
115 }
116 });
117 }
118
119
120 void doIndex(ResourceDto resource, ResourceIndexerMapper mapper) {
121 String key = nameToKey(resource.getName());
122 if (key.length() >= MINIMUM_KEY_SIZE) {
123 ResourceIndexDto dto = new ResourceIndexDto()
124 .setResourceId(resource.getId())
125 .setQualifier(resource.getQualifier())
126 .setRootProjectId(resource.getRootId())
127 .setNameSize(resource.getName().length());
128
129 for (int position = 0; position <= key.length() - MINIMUM_KEY_SIZE; position++) {
130 dto.setPosition(position);
131 dto.setKey(StringUtils.substring(key, position));
132 mapper.insert(dto);
133 }
134 }
135 }
136
137 public boolean indexResource(int id, String name, String qualifier, int rootProjectId) {
138 boolean indexed = false;
139 if (isIndexableQualifier(qualifier)) {
140 SqlSession session = mybatis.openSession();
141 try {
142 String key = nameToKey(name);
143 if (key.length() >= MINIMUM_KEY_SIZE) {
144 indexed = true;
145 ResourceIndexerMapper mapper = session.getMapper(ResourceIndexerMapper.class);
146 boolean toBeIndexed = sanitizeIndex(id, key, mapper);
147 if (toBeIndexed) {
148 ResourceIndexDto dto = new ResourceIndexDto()
149 .setResourceId(id)
150 .setQualifier(qualifier)
151 .setRootProjectId(rootProjectId)
152 .setNameSize(name.length());
153
154 for (int position = 0; position <= key.length() - MINIMUM_KEY_SIZE; position++) {
155 dto.setPosition(position);
156 dto.setKey(StringUtils.substring(key, position));
157 mapper.insert(dto);
158 }
159 session.commit();
160 }
161 }
162 } finally {
163 session.close();
164 }
165 }
166 return indexed;
167 }
168
169
170 /**
171 * Return true if the resource must be indexed, false if the resource is already indexed.
172 * If the resource is indexed with a different key, then this index is dropped and the
173 * resource must be indexed again.
174 */
175 private boolean sanitizeIndex(int resourceId, String key, ResourceIndexerMapper mapper) {
176 ResourceIndexDto masterIndex = mapper.selectMasterIndexByResourceId(resourceId);
177 if (masterIndex != null && !StringUtils.equals(key, masterIndex.getKey())) {
178 // resource has been renamed -> drop existing indexes
179 mapper.deleteByResourceId(resourceId);
180 masterIndex = null;
181 }
182 return masterIndex == null;
183 }
184
185 static String nameToKey(String input) {
186 return StringUtils.lowerCase(StringUtils.trimToEmpty(input));
187 }
188
189 static boolean isIndexableQualifier(String qualifier) {
190 return ArrayUtils.contains(RENAMABLE_QUALIFIERS, qualifier) || ArrayUtils.contains(NOT_RENAMABLE_QUALIFIERS, qualifier);
191 }
192 }