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    }