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