001 /*
002 * Sonar, open source software quality management tool.
003 * Copyright (C) 2008-2012 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.StringUtils;
023 import org.apache.ibatis.session.ResultContext;
024 import org.apache.ibatis.session.ResultHandler;
025 import org.apache.ibatis.session.SqlSession;
026 import org.sonar.api.resources.Qualifiers;
027 import org.sonar.api.resources.Scopes;
028 import org.sonar.core.persistence.MyBatis;
029
030 public class ResourceIndexerDao {
031
032 public static final int MINIMUM_KEY_SIZE = 3;
033
034 // The scopes and qualifiers that are not in the following constants are not indexed at all.
035 // Directories and packages are explicitly excluded.
036 private static final String[] RENAMABLE_QUALIFIERS = {Qualifiers.PROJECT, Qualifiers.MODULE, Qualifiers.VIEW, Qualifiers.SUBVIEW};
037 private static final String[] RENAMABLE_SCOPES = {Scopes.PROJECT};
038 private static final String[] NOT_RENAMABLE_QUALIFIERS = {Qualifiers.FILE, Qualifiers.UNIT_TEST_FILE, Qualifiers.CLASS};
039 private static final String[] NOT_RENAMABLE_SCOPES = {Scopes.FILE};
040
041 private final MyBatis mybatis;
042
043 public ResourceIndexerDao(MyBatis mybatis) {
044 this.mybatis = mybatis;
045 }
046
047 /**
048 * This method is reentrant. It can be executed even if the project is already indexed.
049 */
050 public ResourceIndexerDao indexProject(final int rootProjectId) {
051 SqlSession session = mybatis.openBatchSession();
052 try {
053 ResourceIndexerMapper mapper = session.getMapper(ResourceIndexerMapper.class);
054 doIndexProject(rootProjectId, session, mapper);
055 session.commit();
056 return this;
057
058 } finally {
059 MyBatis.closeQuietly(session);
060 }
061 }
062
063 /**
064 * This method is reentrant. It can be executed even if some projects are already indexed.
065 */
066 public ResourceIndexerDao indexProjects() {
067 final SqlSession session = mybatis.openBatchSession();
068 try {
069 final ResourceIndexerMapper mapper = session.getMapper(ResourceIndexerMapper.class);
070 session.select("org.sonar.core.resource.ResourceIndexerMapper.selectRootProjectIds", /* workaround to get booleans */ResourceIndexerQuery.create(), new ResultHandler() {
071 public void handleResult(ResultContext context) {
072 Integer rootProjectId = (Integer) context.getResultObject();
073 doIndexProject(rootProjectId, session, mapper);
074 session.commit();
075 }
076 });
077 return this;
078
079 } finally {
080 MyBatis.closeQuietly(session);
081 }
082 }
083
084 private void doIndexProject(int rootProjectId, SqlSession session, final ResourceIndexerMapper mapper) {
085 // non indexed resources
086 ResourceIndexerQuery query = ResourceIndexerQuery.create()
087 .setNonIndexedOnly(true)
088 .setQualifiers(NOT_RENAMABLE_QUALIFIERS)
089 .setScopes(NOT_RENAMABLE_SCOPES)
090 .setRootProjectId(rootProjectId);
091
092 session.select("org.sonar.core.resource.ResourceIndexerMapper.selectResources", query, new ResultHandler() {
093 public void handleResult(ResultContext context) {
094 ResourceDto resource = (ResourceDto) context.getResultObject();
095 doIndex(resource, mapper);
096 }
097 });
098
099 // some resources can be renamed, so index must be regenerated
100 // -> delete existing rows and create them again
101 query = ResourceIndexerQuery.create()
102 .setNonIndexedOnly(false)
103 .setQualifiers(RENAMABLE_QUALIFIERS)
104 .setScopes(RENAMABLE_SCOPES)
105 .setRootProjectId(rootProjectId);
106
107 session.select("org.sonar.core.resource.ResourceIndexerMapper.selectResources", query, new ResultHandler() {
108 public void handleResult(ResultContext context) {
109 ResourceDto resource = (ResourceDto) context.getResultObject();
110
111 mapper.deleteByResourceId(resource.getId());
112 doIndex(resource, mapper);
113 }
114 });
115 }
116
117
118 void doIndex(ResourceDto resource, ResourceIndexerMapper mapper) {
119 String key = nameToKey(resource.getName());
120 if (key.length() >= MINIMUM_KEY_SIZE) {
121 ResourceIndexDto dto = new ResourceIndexDto()
122 .setResourceId(resource.getId())
123 .setQualifier(resource.getQualifier())
124 .setRootProjectId(resource.getRootId())
125 .setNameSize(resource.getName().length());
126
127 for (int position = 0; position <= key.length() - MINIMUM_KEY_SIZE; position++) {
128 dto.setPosition(position);
129 dto.setKey(StringUtils.substring(key, position));
130 mapper.insert(dto);
131 }
132 }
133 }
134
135 public boolean indexResource(long id) {
136 boolean indexed = false;
137 SqlSession session = mybatis.openSession();
138 try {
139 ResourceIndexerMapper mapper = session.getMapper(ResourceIndexerMapper.class);
140 ResourceDto resource = mapper.selectResourceToIndex(id);
141 if (resource != null) {
142 Integer rootId = resource.getRootId();
143 if (rootId == null) {
144 rootId = resource.getId().intValue();
145 }
146 indexed = indexResource(resource.getId().intValue(), resource.getName(), resource.getQualifier(), rootId, session, mapper);
147 }
148 return indexed;
149 } finally {
150 MyBatis.closeQuietly(session);
151 }
152 }
153
154 public boolean indexResource(int id, String name, String qualifier, int rootId) {
155 boolean indexed = false;
156 SqlSession session = mybatis.openSession();
157 ResourceIndexerMapper mapper = session.getMapper(ResourceIndexerMapper.class);
158 try {
159 indexed = indexResource(id, name, qualifier, rootId, session, mapper);
160 } finally {
161 MyBatis.closeQuietly(session);
162 }
163 return indexed;
164 }
165
166 private boolean indexResource(int id, String name, String qualifier, int rootId, SqlSession session, ResourceIndexerMapper mapper) {
167 boolean indexed = false;
168 String key = nameToKey(name);
169 if (key.length() >= MINIMUM_KEY_SIZE) {
170 indexed = true;
171 boolean toBeIndexed = sanitizeIndex(id, key, mapper);
172 if (toBeIndexed) {
173 ResourceIndexDto dto = new ResourceIndexDto()
174 .setResourceId(id)
175 .setQualifier(qualifier)
176 .setRootProjectId(rootId)
177 .setNameSize(name.length());
178
179 for (int position = 0; position <= key.length() - MINIMUM_KEY_SIZE; position++) {
180 dto.setPosition(position);
181 dto.setKey(StringUtils.substring(key, position));
182 mapper.insert(dto);
183 }
184 session.commit();
185 }
186 }
187 return indexed;
188 }
189
190
191 /**
192 * Return true if the resource must be indexed, false if the resource is already indexed.
193 * If the resource is indexed with a different key, then this index is dropped and the
194 * resource must be indexed again.
195 */
196 private boolean sanitizeIndex(int resourceId, String key, ResourceIndexerMapper mapper) {
197 ResourceIndexDto masterIndex = mapper.selectMasterIndexByResourceId(resourceId);
198 if (masterIndex != null && !StringUtils.equals(key, masterIndex.getKey())) {
199 // resource has been renamed -> drop existing indexes
200 mapper.deleteByResourceId(resourceId);
201 masterIndex = null;
202 }
203 return masterIndex == null;
204 }
205
206 static String nameToKey(String input) {
207 return StringUtils.lowerCase(StringUtils.trimToEmpty(input));
208 }
209 }