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 }