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 }